WP Job Manager - Version 1.24.0.1

Version Description

Download this release

Release Info

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

Code changes from version 1.31.2 to 1.24.0.1

Files changed (106) hide show
  1. .svnignore +0 -27
  2. assets/css/admin.css +1 -1
  3. assets/css/admin.less +482 -0
  4. assets/css/chosen.css +1 -1
  5. assets/css/chosen.less +435 -0
  6. assets/css/frontend.css +1 -1
  7. assets/css/frontend.less +957 -0
  8. assets/css/icons.less +40 -0
  9. assets/css/job-listings.css +0 -1
  10. assets/css/job-submission.css +0 -1
  11. assets/css/menu.less +17 -0
  12. assets/css/mixins.less +90 -0
  13. assets/css/setup.less +93 -0
  14. assets/js/admin.js +78 -0
  15. assets/js/admin.min.js +1 -1
  16. assets/js/ajax-file-upload.js +90 -0
  17. assets/js/ajax-file-upload.min.js +1 -1
  18. assets/js/ajax-filters.js +289 -0
  19. assets/js/ajax-filters.min.js +1 -1
  20. assets/js/datepicker.min.js +0 -1
  21. assets/js/job-application.js +7 -0
  22. assets/js/job-application.min.js +1 -1
  23. assets/js/job-dashboard.js +7 -0
  24. assets/js/job-dashboard.min.js +1 -1
  25. assets/js/job-submission.js +6 -0
  26. assets/js/job-submission.min.js +1 -1
  27. assets/js/jquery-chosen/chosen.jquery.js +1229 -0
  28. assets/js/jquery-tiptip/jquery.tipTip.js +191 -0
  29. assets/js/jquery-tiptip/jquery.tipTip.min.js +1 -1
  30. assets/js/multiselect.js +3 -0
  31. assets/js/term-multiselect.js +3 -0
  32. changelog.txt +0 -706
  33. includes/3rd-party/3rd-party.php +0 -11
  34. includes/3rd-party/all-in-one-seo-pack.php +0 -25
  35. includes/3rd-party/jetpack.php +0 -38
  36. includes/3rd-party/polylang.php +0 -67
  37. includes/3rd-party/rp4wp.php +0 -56
  38. includes/3rd-party/wp-all-import.php +0 -25
  39. includes/3rd-party/wpml.php +0 -103
  40. includes/3rd-party/yoast.php +0 -29
  41. includes/abstracts/abstract-wp-job-manager-email-template.php +0 -133
  42. includes/abstracts/abstract-wp-job-manager-email.php +0 -231
  43. includes/abstracts/abstract-wp-job-manager-form.php +67 -281
  44. includes/admin/class-wp-job-manager-addons.php +43 -124
  45. includes/admin/class-wp-job-manager-admin.php +34 -125
  46. includes/admin/class-wp-job-manager-cpt-legacy.php +0 -91
  47. includes/admin/class-wp-job-manager-cpt.php +255 -575
  48. includes/admin/class-wp-job-manager-permalink-settings.php +0 -135
  49. includes/admin/class-wp-job-manager-settings.php +205 -684
  50. includes/admin/class-wp-job-manager-setup.php +87 -209
  51. includes/admin/class-wp-job-manager-taxonomy-meta.php +0 -158
  52. includes/admin/class-wp-job-manager-writepanels.php +136 -421
  53. includes/admin/views/html-admin-page-addons.php +0 -72
  54. includes/class-wp-job-manager-ajax.php +115 -181
  55. includes/class-wp-job-manager-api.php +27 -50
  56. includes/class-wp-job-manager-cache-helper.php +15 -149
  57. includes/class-wp-job-manager-category-walker.php +17 -45
  58. includes/class-wp-job-manager-data-cleaner.php +0 -367
  59. includes/class-wp-job-manager-data-exporter.php +0 -89
  60. includes/class-wp-job-manager-email-notifications.php +0 -827
  61. includes/class-wp-job-manager-forms.php +13 -39
  62. includes/class-wp-job-manager-geocode.php +66 -147
  63. includes/class-wp-job-manager-install.php +56 -98
  64. includes/class-wp-job-manager-post-types.php +312 -518
  65. includes/class-wp-job-manager-shortcodes.php +253 -372
  66. includes/class-wp-job-manager-usage-tracking-data.php +0 -315
  67. includes/class-wp-job-manager-usage-tracking.php +0 -219
  68. includes/class-wp-job-manager-widget.php +0 -220
  69. includes/class-wp-job-manager-widgets.php +336 -0
  70. includes/emails/class-wp-job-manager-email-admin-expiring-job.php +0 -52
  71. includes/emails/class-wp-job-manager-email-admin-new-job.php +0 -91
  72. includes/emails/class-wp-job-manager-email-admin-updated-job.php +0 -91
  73. includes/emails/class-wp-job-manager-email-employer-expiring-job.php +0 -159
  74. includes/forms/class-wp-job-manager-form-edit-job.php +38 -139
  75. includes/forms/class-wp-job-manager-form-submit-job.php +262 -415
  76. includes/helper/class-wp-job-manager-helper-api.php +0 -176
  77. includes/helper/class-wp-job-manager-helper-options.php +0 -115
  78. includes/helper/class-wp-job-manager-helper.php +0 -609
  79. includes/helper/views/html-licence-key-error.php +0 -9
  80. includes/helper/views/html-licence-key-notice.php +0 -9
  81. includes/helper/views/html-licences.php +0 -81
  82. includes/rest-api/class-wp-job-manager-controllers-status.php +0 -132
  83. includes/rest-api/class-wp-job-manager-data-stores-status.php +0 -72
  84. includes/rest-api/class-wp-job-manager-filters-status.php +0 -46
  85. includes/rest-api/class-wp-job-manager-models-job-categories-custom-fields.php +0 -26
  86. includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php +0 -104
  87. includes/rest-api/class-wp-job-manager-models-job-types-custom-fields.php +0 -64
  88. includes/rest-api/class-wp-job-manager-models-settings.php +0 -109
  89. includes/rest-api/class-wp-job-manager-models-status.php +0 -50
  90. includes/rest-api/class-wp-job-manager-registrable-job-categories.php +0 -42
  91. includes/rest-api/class-wp-job-manager-registrable-job-listings.php +0 -238
  92. includes/rest-api/class-wp-job-manager-registrable-job-types.php +0 -42
  93. includes/rest-api/class-wp-job-manager-registrable-taxonomy-type.php +0 -239
  94. includes/rest-api/class-wp-job-manager-rest-api.php +0 -128
  95. includes/widgets/class-wp-job-manager-widget-featured-jobs.php +0 -138
  96. includes/widgets/class-wp-job-manager-widget-recent-jobs.php +0 -155
  97. languages/wp-job-manager.pot +594 -1464
  98. lib/emogrifier/class-emogrifier.php +0 -1557
  99. lib/usage-tracking/class-usage-tracking-base.php +0 -581
  100. lib/usage-tracking/tests/support/class-usage-tracking-test-subclass.php +0 -67
  101. lib/usage-tracking/tests/support/wp-die-exception.php +0 -18
  102. lib/usage-tracking/tests/test-class-usage-tracking.php +0 -527
  103. lib/wpjm_rest/class-wp-job-manager-rest-bootstrap.php +0 -196
  104. lib/wpjm_rest/class-wp-job-manager-rest-classloader.php +0 -145
  105. lib/wpjm_rest/class-wp-job-manager-rest-controller.php +0 -285
  106. lib/wpjm_rest/class-wp-job-manager-rest-environment.php +0 -420
.svnignore DELETED
@@ -1,27 +0,0 @@
1
- .eslintrc
2
- .eslintignore
3
- .codeclimate.yml
4
- .git
5
- .gitignore
6
- .sass-cache
7
- .jshintrc
8
- .jshintignore
9
- .travis.yml
10
- .npmrc
11
- .nvmrc
12
- .editorconfig
13
- .eslintrc
14
- readme.md
15
- .github
16
- phpunit.xml.dist
17
- phpcs.ruleset.xml
18
- docs
19
- tests
20
- tools
21
- package.json
22
- Gruntfile.js
23
- node_modules
24
- vendor
25
- composer.lock
26
- composer.json
27
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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}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}}
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}.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;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;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;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;text-align:center;font-variant:normal;text-transform:none;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{padding:12px;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}#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_addons_wrap #job-manager-addons-banner{position:relative;background:#d85677;padding:0 2em 0 5em;color:#fff;margin:10px .25% 20px 0;border-color:rgba(0,0,0,.1);overflow:hidden}.wp_job_manager_addons_wrap #job-manager-addons-banner strong{font-size:1.25em;line-height:.8em;text-shadow:0 2px 0 rgba(0,0,0,.1);font-weight:400;float:left;padding:1.6em 0}.wp_job_manager_addons_wrap #job-manager-addons-banner a.button{color:#fff;text-decoration:none;font-weight:700;float:right;background:#d85677;border:1px solid #fff;line-height:1em;padding:1em;margin:1em 0;text-shadow:0 2px 0 rgba(0,0,0,.1);box-shadow:0 2px 0 rgba(0,0,0,.1);height:auto;position:relative}.wp_job_manager_addons_wrap #job-manager-addons-banner:before{display:inline-block;-webkit-font-smoothing:antialiased;vertical-align:top;font-family:jm-logo;content:"\e600";top:.02em;left:0;position:absolute;text-shadow:0 2px 0 rgba(0,0,0,.1);font-size:5em;font-weight:400;text-align:center;width:1em;height:1em;line-height:1em}.wp_job_manager_addons_wrap .products{overflow:hidden}.wp_job_manager_addons_wrap .products li{display:inline-block;margin:0 1% 10px 0!important;padding:0;vertical-align:top;width:24%;min-width:250px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border:1px solid #ddd;-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);overflow:hidden;position:relative;opacity:.8}.wp_job_manager_addons_wrap .products li:nth-child(4n+0){margin-right:0!important}.wp_job_manager_addons_wrap .products li a{color:inherit;text-decoration:none}.wp_job_manager_addons_wrap .products li img{width:100%;height:auto;display:block;padding:0;margin:0;background:#fff;border-bottom:1px solid rgba(0,0,0,.1)}.wp_job_manager_addons_wrap .products li h2{margin:0!important;padding:10px 0!important;line-height:1;background:rgba(255,255,255,.6);border-bottom:1px solid rgba(0,0,0,.1);color:#000;text-align:center;position:absolute;width:100%;top:0;left:0;font-size:16px;text-shadow:none;display:none}.wp_job_manager_addons_wrap .products li:focus,.wp_job_manager_addons_wrap .products li:hover{opacity:1}.wp_job_manager_addons_wrap .products li:focus h2,.wp_job_manager_addons_wrap .products li:hover h2{display:block}.wp_job_manager_addons_wrap .products li .third_party{display:none}.wp_job_manager_addons_wrap .products li p{padding:20px!important;margin:0!important;border-top:1px solid #f1f1f1}.wp_job_manager_addons_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}
assets/css/admin.less ADDED
@@ -0,0 +1,482 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "mixins";
2
+ @import "icons.less";
3
+
4
+ .job-manager-settings-wrap {
5
+ .updated {
6
+ display: none;
7
+ }
8
+ .job-manager-updated {
9
+ display: block;
10
+ margin: 1em 0 0;
11
+ }
12
+ }
13
+ .widefat {
14
+ td.column-featured_job, td.column-filled, td.column-job_status {
15
+ width: 46px;
16
+ text-align: left;
17
+ padding-left: 11px;
18
+ }
19
+ th.column-featured_job, th.column-filled, th.column-job_status {
20
+ width: 1em;
21
+ span {
22
+ display: block;
23
+ width: 1em;
24
+ height: 1em;
25
+ line-height: 1em;
26
+ padding: 1px 0 0 0;
27
+ overflow: hidden;
28
+ &:before {
29
+ content: '\e803';
30
+ .jm-icon;
31
+ }
32
+ }
33
+ }
34
+ th.column-filled {
35
+ span {
36
+ &:before {
37
+ content: '\e807';
38
+ }
39
+ }
40
+ }
41
+ th.column-job_status {
42
+ span {
43
+ &:before {
44
+ content: '\e828';
45
+ }
46
+ }
47
+ }
48
+ .column-job_posted {
49
+ strong {
50
+ display: block;
51
+ margin-bottom: .2em;
52
+ }
53
+ }
54
+ td.column-job_status {
55
+ span {
56
+ position: relative;
57
+ font-size: 1em;
58
+ line-height: 1.5em;
59
+ width: 1em;
60
+ height: 0;
61
+ padding: 2em 0 0 0;
62
+ overflow: hidden;
63
+ display: block;
64
+ }
65
+ span:before {
66
+ .jm-icon;
67
+ position: absolute;
68
+ top: 0;
69
+ left: 0;
70
+ line-height: 1.5em;
71
+ vertical-align: middle;
72
+ color: #999;
73
+ content: '\e829';
74
+ }
75
+ .status-trash:before {
76
+ content: '\e82b';
77
+ color: #a00;
78
+ }
79
+ .status-pending:before {
80
+ content: '\e82c';
81
+ color: #ffba00;
82
+ }
83
+ .status-publish:before {
84
+ content: '\e82f';
85
+ color: #73a724;
86
+ }
87
+ .status-expired:before {
88
+ content: '\e82e';
89
+ color: #a00;
90
+ }
91
+ }
92
+ .column-job_listing_type {
93
+ text-align: left;
94
+ width: 6em;
95
+ word-wrap: normal !important;
96
+ .job-type {
97
+ color: #fff;
98
+ padding: 4px;
99
+ font-size: 11px;
100
+ .border_radius( 2px );
101
+ display:block;
102
+ background-color: @part-time;
103
+ text-align: center;
104
+ }
105
+ .full-time {
106
+ background-color: @full-time;
107
+ }
108
+ .part-time {
109
+ background-color: @part-time;
110
+ }
111
+ .temporary {
112
+ background-color: @temporary;
113
+ }
114
+ .freelance {
115
+ background-color: @freelance;
116
+ }
117
+ .internship {
118
+ background-color: @internship;
119
+ }
120
+ }
121
+ th.column-job_position {
122
+ width: 20%;
123
+ }
124
+ td.column-job_position {
125
+ width: 20%;
126
+ height: 34px;
127
+ .job_position {
128
+ position: relative;
129
+ padding-right: 50px !important;
130
+ }
131
+ a.job_title {
132
+ font-weight: bold;
133
+ }
134
+ img {
135
+ width: 32px;
136
+ height: 32px;
137
+ position: absolute;
138
+ right: 7px;
139
+ top: 0;
140
+ .border_radius( 50% );
141
+ .box_shadow( 0, 1px, 0, 1px, rgba( 0,0,0,0.1 ) );
142
+ border: 1px solid #fff;
143
+ }
144
+ .company {
145
+ margin-top: .2em;
146
+ display: block;
147
+ padding-top: 2px;
148
+ color: #bbbbbb;
149
+ }
150
+ }
151
+ .column-job_location {
152
+ width: 10%;
153
+ }
154
+ .column-job_actions {
155
+ text-align: right;
156
+ width: 128px;
157
+ strong {
158
+ display: block;
159
+ margin-bottom: .2em;
160
+ }
161
+ .actions {
162
+ padding-top: 2px;
163
+ }
164
+ a.button {
165
+ display: inline-block;
166
+ margin: 0 0 2px 4px;
167
+ cursor: pointer;
168
+ padding: 0 6px !important;
169
+ font-size: 1em !important;
170
+ line-height: 2em !important;
171
+ overflow: hidden;
172
+ }
173
+ a.button-icon {
174
+ width: 2em !important;
175
+ padding: 0 !important;
176
+ &:before {
177
+ .jm-icon;
178
+ float: left;
179
+ width: 2em !important;
180
+ line-height: 2em;
181
+ }
182
+ }
183
+ .icon-view:before {
184
+ content: '\e805';
185
+ }
186
+ .icon-edit:before {
187
+ content: '\e804';
188
+ }
189
+ .icon-delete:before {
190
+ content: '\e82b';
191
+ }
192
+ .icon-approve:before {
193
+ content: '\e802';
194
+ }
195
+ }
196
+ }
197
+ .wp_job_manager_meta_data {
198
+ padding: 12px;
199
+ zoom: 1;
200
+ &:before,
201
+ &:after {
202
+   content: "";
203
+   display: table;
204
+ }
205
+ &:after {
206
+   clear: both;
207
+ }
208
+ .form-field {
209
+ width: 50%;
210
+ line-height: 2em;
211
+ float: left;
212
+ box-sizing: border-box;
213
+ padding: 0 12px 0 0;
214
+ margin: 0 0 12px;
215
+ clear: both;
216
+ &:nth-child(even) {
217
+ float: right;
218
+ padding: 0 0 0 12px;
219
+ clear: right;
220
+ }
221
+ &:nth-last-child(-n+2) {
222
+ margin-bottom: 0;
223
+ padding-bottom: 0;
224
+ border-bottom: 0;
225
+ }
226
+ label {
227
+ vertical-align: middle;
228
+ display: block;
229
+ font-weight: bold;
230
+ margin: 0;
231
+ }
232
+ .tips {
233
+ cursor: help;
234
+ float: right;
235
+ font-weight: normal;
236
+ color: #999;
237
+ }
238
+ input {
239
+ width: 100%;
240
+ margin: 1px 0;
241
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
242
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
243
+ box-sizing: border-box; /* Opera/IE 8+ */
244
+ vertical-align: middle;
245
+ }
246
+ input.checkbox, input.radio {
247
+ width: auto;
248
+ margin: 4px 2px;
249
+ display: inline-block;
250
+ }
251
+ .description {
252
+ display: block;
253
+ color: #999;
254
+ }
255
+ &.form-field-checkbox .description {
256
+ display: inline;
257
+ }
258
+ .file_url {
259
+ input {
260
+ width: 75%;
261
+ }
262
+ }
263
+ .button {
264
+ margin-left: 4px;
265
+ }
266
+ }
267
+ }
268
+
269
+ /* TipTip CSS - Version 1.2 */
270
+ #tiptip_holder {
271
+ display: none;
272
+ position: absolute;
273
+ top: 0;
274
+ left: 0;
275
+ z-index: 99999;
276
+ }
277
+ #tiptip_holder.tip_top {
278
+ padding-bottom: 5px;
279
+ }
280
+ #tiptip_holder.tip_bottom {
281
+ padding-top: 5px;
282
+ }
283
+ #tiptip_holder.tip_right {
284
+ padding-left: 5px;
285
+ }
286
+ #tiptip_holder.tip_left {
287
+ padding-right: 5px;
288
+ }
289
+ #tiptip_content {
290
+ font-size: 11px;
291
+ color: #fff;
292
+ padding: 4px 8px;
293
+ background:#464646;
294
+ border-radius: 3px;
295
+ -webkit-border-radius: 3px;
296
+ -moz-border-radius: 3px;
297
+ box-shadow: 1px 1px 3px rgba(0,0,0,0.10);
298
+ -webkit-box-shadow: 1px 1px 3px rgba(0,0,0,0.10);
299
+ -moz-box-shadow: 1px 1px 3px rgba(0,0,0,0.10);
300
+ text-align: center;
301
+ code {
302
+ background: #999;
303
+ padding: 1px;
304
+ }
305
+ }
306
+ #tiptip_arrow, #tiptip_arrow_inner {
307
+ position: absolute;
308
+ border-color: transparent;
309
+ border-style: solid;
310
+ border-width: 6px;
311
+ height: 0;
312
+ width: 0;
313
+ }
314
+ #tiptip_holder.tip_top #tiptip_arrow_inner {
315
+ margin-top: -7px;
316
+ margin-left: -6px;
317
+ border-top-color: #464646;
318
+ }
319
+
320
+ #tiptip_holder.tip_bottom #tiptip_arrow_inner {
321
+ margin-top: -5px;
322
+ margin-left: -6px;
323
+ border-bottom-color: #464646;
324
+ }
325
+
326
+ #tiptip_holder.tip_right #tiptip_arrow_inner {
327
+ margin-top: -6px;
328
+ margin-left: -5px;
329
+ border-right-color: #464646;
330
+ }
331
+
332
+ #tiptip_holder.tip_left #tiptip_arrow_inner {
333
+ margin-top: -6px;
334
+ margin-left: -7px;
335
+ border-left-color: #464646;
336
+ }
337
+
338
+ /* Addons */
339
+ .wp_job_manager_addons_wrap {
340
+ #job-manager-addons-banner {
341
+ position: relative;
342
+ background: #d85677;
343
+ padding: 0 2em 0 5em;
344
+ color: #fff;
345
+ margin: 10px .25% 20px 0;
346
+ border-color: rgba(0,0,0,.1);
347
+ overflow: hidden;
348
+ strong {
349
+ font-size: 1.25em;
350
+ line-height: 1/1.25em;
351
+ text-shadow: 0 2px 0 rgba(0,0,0,.1);
352
+ font-weight: normal;
353
+ float: left;
354
+ padding: 1/1.25*2em 0;
355
+ }
356
+ a.button {
357
+ color: #fff;
358
+ text-decoration: none;
359
+ font-weight: bold;
360
+ float: right;
361
+ background: #d85677;
362
+ border: 1px solid #fff;
363
+ line-height: 1em;
364
+ padding: 1em;
365
+ margin: 1em 0;
366
+ text-shadow: 0 2px 0 rgba(0,0,0,.1);
367
+ box-shadow: 0 2px 0 rgba(0,0,0,.1);
368
+ height: auto;
369
+ position: relative;
370
+ }
371
+ &:before {
372
+ display: inline-block;
373
+ -webkit-font-smoothing: antialiased;
374
+ vertical-align: top;
375
+ font-family: 'jm-logo';
376
+ content: "\e600";
377
+ top: .02em;
378
+ left: 0;
379
+ position: absolute;
380
+ text-shadow: 0 2px 0 rgba(0,0,0,0.1);
381
+ font-size: 5em;
382
+ font-weight: normal;
383
+ text-align: center;
384
+ width: 1em;
385
+ height: 1em;
386
+ line-height: 1em;
387
+ }
388
+ }
389
+ .products {
390
+ overflow: hidden;
391
+ li {
392
+ display: inline-block;
393
+ margin: 0 1% 10px 0 !important;
394
+ padding: 0;
395
+ vertical-align: top;
396
+ width: 24%;
397
+ min-width: 250px;
398
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
399
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
400
+ box-sizing: border-box; /* Opera/IE 8+ */
401
+ border: 1px solid #ddd;
402
+ -webkit-box-shadow:
403
+ inset 0 1px 0 rgba(255,255,255,0.2),
404
+ inset 0 -1px 0 rgba(0,0,0,0.1);
405
+ box-shadow:
406
+ inset 0 1px 0 rgba(255,255,255,0.2),
407
+ inset 0 -1px 0 rgba(0,0,0,0.1);
408
+ overflow: hidden;
409
+ position: relative;
410
+ opacity: 0.8;
411
+ &:nth-child(4n+0) {
412
+ margin-right: 0 !important;
413
+ }
414
+ a {
415
+ color: inherit;
416
+ text-decoration: none;
417
+ }
418
+ img {
419
+ width: 100%;
420
+ height: auto;
421
+ display: block;
422
+ padding: 0;
423
+ margin: 0;
424
+ background: #fff;
425
+ border-bottom: 1px solid rgba(0,0,0,0.1);
426
+ }
427
+ h2 {
428
+ margin: 0 !important;
429
+ padding: 10px 0 !important;
430
+ line-height: 1;
431
+ background: rgba(255,255,255,0.6);
432
+ border-bottom: 1px solid rgba(0,0,0,0.1);
433
+ color: #000;
434
+ text-align: center;
435
+ position: absolute;
436
+ width: 100%;
437
+ top: 0;
438
+ left: 0;
439
+ font-size: 16px;
440
+ text-shadow: none;
441
+ display: none;
442
+ }
443
+ &:hover, &:focus {
444
+ opacity: 1;
445
+ h2 {
446
+ display: block;
447
+ }
448
+ }
449
+ .third_party {
450
+ display: none;
451
+ }
452
+ p {
453
+ padding: 20px !important;
454
+ margin: 0 !important;
455
+ border-top: 1px solid #f1f1f1;
456
+ }
457
+ .price {
458
+ display: none;
459
+ }
460
+ }
461
+ }
462
+ }
463
+ .rtl {
464
+ .widefat {
465
+ .column-job_actions {
466
+ a.button-icon {
467
+ &:before {
468
+ float: right;
469
+ }
470
+ }
471
+ }
472
+ }
473
+ .wp_job_manager_meta_data {
474
+ p {
475
+ padding: 0 20% 0 0;
476
+ }
477
+ label {
478
+ left: auto;
479
+ right: 0;
480
+ }
481
+ }
482
+ }
assets/css/chosen.css CHANGED
@@ -8,4 +8,4 @@ Copyright (c) 2011 Harvest http://getharvest.com
8
 
9
  MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
10
  This file is generated by `grunt build`, do not edit it by hand.
11
- */.chosen-container{position:relative;display:inline-block;vertical-align:middle;font-size:13px;zoom:1;-webkit-user-select:none;-moz-user-select:none;user-select:none}.chosen-container .chosen-drop{position:absolute;top:100%;left:-9999px;z-index:1010;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;border:1px solid #aaa;border-top:0;background:#fff;box-shadow:0 4px 5px rgba(0,0,0,.15)}.chosen-container.chosen-with-drop .chosen-drop{left:0}.chosen-container a{cursor:pointer}.chosen-container-single .chosen-single{position:relative;display:block;overflow:hidden;padding:0 0 0 8px;height:23px;border:1px solid #aaa;border-radius:5px;background-color:#fff;background:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#fff),color-stop(50%,#f6f6f6),color-stop(52%,#eee),color-stop(100%,#f4f4f4));background:-webkit-linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background:-moz-linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background:-o-linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background:linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background-clip:padding-box;box-shadow:0 0 3px #fff inset,0 1px 1px rgba(0,0,0,.1);color:#444;text-decoration:none;white-space:nowrap;line-height:24px}.chosen-container-single .chosen-default{color:#999}.chosen-container-single .chosen-single span{display:block;overflow:hidden;margin-right:26px;text-overflow:ellipsis;white-space:nowrap}.chosen-container-single .chosen-single-with-deselect span{margin-right:38px}.chosen-container-single .chosen-single abbr{position:absolute;top:6px;right:26px;display:block;width:12px;height:12px;background:url(../images/chosen-sprite.png) -42px 1px no-repeat;font-size:1px}.chosen-container-single .chosen-single abbr:hover{background-position:-42px -10px}.chosen-container-single.chosen-disabled .chosen-single abbr:hover{background-position:-42px -10px}.chosen-container-single .chosen-single div{position:absolute;top:0;right:0;display:block;width:18px;height:100%}.chosen-container-single .chosen-single div b{display:block;width:100%;height:100%;background:url(../images/chosen-sprite.png) no-repeat 0 2px}.chosen-container-single .chosen-search{position:relative;z-index:1010;margin:0;padding:3px 4px;white-space:nowrap}.chosen-container-single .chosen-search input[type=text]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:1px 0;padding:4px 20px 4px 5px;width:100%;height:auto;outline:0;border:1px solid #aaa;background:#fff url(../images/chosen-sprite.png) no-repeat 100% -20px;background:url(../images/chosen-sprite.png) no-repeat 100% -20px;font-size:1em;font-family:sans-serif;line-height:normal;border-radius:0}.chosen-container-single .chosen-drop{margin-top:-1px;border-radius:0 0 4px 4px;background-clip:padding-box}.chosen-container-single.chosen-container-single-nosearch .chosen-search{position:absolute;left:-9999px}.chosen-container .chosen-results{position:relative;overflow-x:hidden;overflow-y:auto;margin:0 4px 4px 0;padding:0 0 0 4px;max-height:240px;-webkit-overflow-scrolling:touch}.chosen-container .chosen-results li{display:none;margin:0;padding:5px 6px;list-style:none;line-height:15px;-webkit-touch-callout:none}.chosen-container .chosen-results li.active-result{display:list-item;cursor:pointer}.chosen-container .chosen-results li.disabled-result{display:list-item;color:#ccc;cursor:default}.chosen-container .chosen-results li.highlighted{background-color:#3875d7;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#3875d7),color-stop(90%,#2a62bc));background-image:-webkit-linear-gradient(#3875d7 20%,#2a62bc 90%);background-image:-moz-linear-gradient(#3875d7 20%,#2a62bc 90%);background-image:-o-linear-gradient(#3875d7 20%,#2a62bc 90%);background-image:linear-gradient(#3875d7 20%,#2a62bc 90%);color:#fff}.chosen-container .chosen-results li.no-results{display:list-item;background:#f4f4f4}.chosen-container .chosen-results li.group-result{display:list-item;font-weight:700;cursor:default}.chosen-container .chosen-results li.group-option{padding-left:15px}.chosen-container .chosen-results li em{font-style:normal;text-decoration:underline}.chosen-container-multi .chosen-choices{position:relative;overflow:hidden;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;width:100%;height:auto!important;height:1%;border:1px solid #aaa;background-color:#fff;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(1%,#eee),color-stop(15%,#fff));background-image:-webkit-linear-gradient(#eee 1%,#fff 15%);background-image:-moz-linear-gradient(#eee 1%,#fff 15%);background-image:-o-linear-gradient(#eee 1%,#fff 15%);background-image:linear-gradient(#eee 1%,#fff 15%);cursor:text}.chosen-container-multi .chosen-choices li{float:left;list-style:none}.chosen-container-multi .chosen-choices li.search-field{margin:0;padding:0;white-space:nowrap}.chosen-container-multi .chosen-choices li.search-field input[type=text]{margin:1px 0;padding:5px;height:auto;outline:0;border:0!important;background:0 0!important;box-shadow:none;color:#666;font-size:100%;font-family:sans-serif;line-height:normal;border-radius:0}.chosen-container-multi .chosen-choices li.search-field .default{color:#999}.chosen-container-multi .chosen-choices li.search-choice{position:relative;margin:3px 0 3px 5px;padding:3px 20px 3px 5px;border:1px solid #aaa;border-radius:3px;background-color:#e4e4e4;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#f4f4f4),color-stop(50%,#f0f0f0),color-stop(52%,#e8e8e8),color-stop(100%,#eee));background-image:-webkit-linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-moz-linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-o-linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-clip:padding-box;box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);color:#333;line-height:13px;cursor:default}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close{position:absolute;top:4px;right:3px;display:block;width:12px;height:12px;background:url(../images/chosen-sprite.png) -42px 1px no-repeat;font-size:1px}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover{background-position:-42px -10px}.chosen-container-multi .chosen-choices li.search-choice-disabled{padding-right:5px;border:1px solid #ccc;background-color:#e4e4e4;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#f4f4f4),color-stop(50%,#f0f0f0),color-stop(52%,#e8e8e8),color-stop(100%,#eee));background-image:-webkit-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-moz-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-o-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);color:#666}.chosen-container-multi .chosen-choices li.search-choice-focus{background:#d4d4d4}.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close{background-position:-42px -10px}.chosen-container-multi .chosen-results{margin:0;padding:0}.chosen-container-multi .chosen-drop .result-selected{display:list-item;color:#ccc;cursor:default}.chosen-container-active .chosen-single{border:1px solid #5897fb;box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active.chosen-with-drop .chosen-single{border:1px solid #aaa;-moz-border-radius-bottomright:0;border-bottom-right-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#eee),color-stop(80%,#fff));background-image:-webkit-linear-gradient(#eee 20%,#fff 80%);background-image:-moz-linear-gradient(#eee 20%,#fff 80%);background-image:-o-linear-gradient(#eee 20%,#fff 80%);background-image:linear-gradient(#eee 20%,#fff 80%);box-shadow:0 1px 0 #fff inset}.chosen-container-active.chosen-with-drop .chosen-single div{border-left:none;background:0 0}.chosen-container-active.chosen-with-drop .chosen-single div b{background-position:-18px 2px}.chosen-container-active .chosen-choices{border:1px solid #5897fb;box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active .chosen-choices li.search-field input[type=text]{color:#111!important}.chosen-disabled{opacity:.5!important;cursor:default}.chosen-disabled .chosen-single{cursor:default}.chosen-disabled .chosen-choices .search-choice .search-choice-close{cursor:default}.chosen-rtl{text-align:right}.chosen-rtl .chosen-single{overflow:visible;padding:0 8px 0 0}.chosen-rtl .chosen-single span{margin-right:0;margin-left:26px;direction:rtl}.chosen-rtl .chosen-single-with-deselect span{margin-left:38px}.chosen-rtl .chosen-single div{right:auto;left:3px}.chosen-rtl .chosen-single abbr{right:auto;left:26px}.chosen-rtl .chosen-choices li{float:right}.chosen-rtl .chosen-choices li.search-field input[type=text]{direction:rtl}.chosen-rtl .chosen-choices li.search-choice{margin:3px 5px 3px 0;padding:3px 5px 3px 19px}.chosen-rtl .chosen-choices li.search-choice .search-choice-close{right:auto;left:4px}.chosen-rtl .chosen-drop,.chosen-rtl.chosen-container-single-nosearch .chosen-search{left:9999px}.chosen-rtl.chosen-container-single .chosen-results{margin:0 0 4px 4px;padding:0 4px 0 0}.chosen-rtl .chosen-results li.group-option{padding-right:15px;padding-left:0}.chosen-rtl.chosen-container-active.chosen-with-drop .chosen-single div{border-right:none}.chosen-rtl .chosen-search input[type=text]{padding:4px 5px 4px 20px;background:#fff url(../images/chosen-sprite.png) no-repeat -30px -20px;background:url(../images/chosen-sprite.png) no-repeat -30px -20px;direction:rtl}.chosen-rtl.chosen-container-single .chosen-single div b{background-position:6px 2px}.chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b{background-position:-12px 2px}@media only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-resolution:144dpi){.chosen-container .chosen-results-scroll-down span,.chosen-container .chosen-results-scroll-up span,.chosen-container-multi .chosen-choices .search-choice .search-choice-close,.chosen-container-single .chosen-search input[type=text],.chosen-container-single .chosen-single abbr,.chosen-container-single .chosen-single div b,.chosen-rtl .chosen-search input[type=text]{background-image:url(../images/chosen-sprite@2x.png)!important;background-size:52px 37px!important;background-repeat:no-repeat!important}}
8
 
9
  MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
10
  This file is generated by `grunt build`, do not edit it by hand.
11
+ */.chosen-container{position:relative;display:inline-block;vertical-align:middle;font-size:13px;zoom:1;*display:inline;-webkit-user-select:none;-moz-user-select:none;user-select:none}.chosen-container .chosen-drop{position:absolute;top:100%;left:-9999px;z-index:1010;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;border:1px solid #aaa;border-top:0;background:#fff;box-shadow:0 4px 5px rgba(0,0,0,.15)}.chosen-container.chosen-with-drop .chosen-drop{left:0}.chosen-container a{cursor:pointer}.chosen-container-single .chosen-single{position:relative;display:block;overflow:hidden;padding:0 0 0 8px;height:23px;border:1px solid #aaa;border-radius:5px;background:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#fff),color-stop(50%,#f6f6f6),color-stop(52%,#eee),color-stop(100%,#f4f4f4));background:-webkit-linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background:-moz-linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background:-o-linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background:linear-gradient(top,#fff 20%,#f6f6f6 50%,#eee 52%,#f4f4f4 100%);background-clip:padding-box;box-shadow:0 0 3px #fff inset,0 1px 1px rgba(0,0,0,.1);color:#444;text-decoration:none;white-space:nowrap;line-height:24px}.chosen-container-single .chosen-default{color:#999}.chosen-container-single .chosen-single span{display:block;overflow:hidden;margin-right:26px;text-overflow:ellipsis;white-space:nowrap}.chosen-container-single .chosen-single-with-deselect span{margin-right:38px}.chosen-container-single .chosen-single abbr{position:absolute;top:6px;right:26px;display:block;width:12px;height:12px;background:url(../images/chosen-sprite.png) -42px 1px no-repeat;font-size:1px}.chosen-container-single .chosen-single abbr:hover,.chosen-container-single.chosen-disabled .chosen-single abbr:hover{background-position:-42px -10px}.chosen-container-single .chosen-single div{position:absolute;top:0;right:0;display:block;width:18px;height:100%}.chosen-container-single .chosen-single div b{display:block;width:100%;height:100%;background:url(../images/chosen-sprite.png) no-repeat 0 2px}.chosen-container-single .chosen-search{position:relative;z-index:1010;margin:0;padding:3px 4px;white-space:nowrap}.chosen-container-single .chosen-search input[type=text]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:1px 0;padding:4px 20px 4px 5px;width:100%;height:auto;outline:0;border:1px solid #aaa;background:#fff url(../images/chosen-sprite.png) no-repeat 100% -20px;background:url(../images/chosen-sprite.png) no-repeat 100% -20px;font-size:1em;font-family:sans-serif;line-height:normal;border-radius:0}.chosen-container-single .chosen-drop{margin-top:-1px;border-radius:0 0 4px 4px;background-clip:padding-box}.chosen-container-single.chosen-container-single-nosearch .chosen-search{position:absolute;left:-9999px}.chosen-container .chosen-results{position:relative;overflow-x:hidden;overflow-y:auto;margin:0 4px 4px 0;padding:0 0 0 4px;max-height:240px;-webkit-overflow-scrolling:touch}.chosen-container .chosen-results li{display:none;margin:0;padding:5px 6px;list-style:none;line-height:15px;-webkit-touch-callout:none}.chosen-container .chosen-results li.active-result{display:list-item;cursor:pointer}.chosen-container .chosen-results li.disabled-result{display:list-item;color:#ccc;cursor:default}.chosen-container .chosen-results li.highlighted{background-color:#3875d7;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#3875d7),color-stop(90%,#2a62bc));background-image:-webkit-linear-gradient(#3875d7 20%,#2a62bc 90%);background-image:-moz-linear-gradient(#3875d7 20%,#2a62bc 90%);background-image:-o-linear-gradient(#3875d7 20%,#2a62bc 90%);background-image:linear-gradient(#3875d7 20%,#2a62bc 90%);color:#fff}.chosen-container .chosen-results li.no-results{display:list-item;background:#f4f4f4}.chosen-container .chosen-results li.group-result{display:list-item;font-weight:700;cursor:default}.chosen-container .chosen-results li.group-option{padding-left:15px}.chosen-container .chosen-results li em{font-style:normal;text-decoration:underline}.chosen-container-multi .chosen-choices{position:relative;overflow:hidden;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin:0;padding:0;width:100%;height:auto!important;height:1%;border:1px solid #aaa;background-color:#fff;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(1%,#eee),color-stop(15%,#fff));background-image:-webkit-linear-gradient(#eee 1%,#fff 15%);background-image:-moz-linear-gradient(#eee 1%,#fff 15%);background-image:-o-linear-gradient(#eee 1%,#fff 15%);background-image:linear-gradient(#eee 1%,#fff 15%);cursor:text}.chosen-container-multi .chosen-choices li{float:left;list-style:none}.chosen-container-multi .chosen-choices li.search-field{margin:0;padding:0;white-space:nowrap}.chosen-container-multi .chosen-choices li.search-field input[type=text]{margin:1px 0;padding:5px;height:auto;outline:0;border:0!important;background:transparent!important;box-shadow:none;color:#666;font-size:100%;font-family:sans-serif;line-height:normal;border-radius:0}.chosen-container-multi .chosen-choices li.search-field .default{color:#999}.chosen-container-multi .chosen-choices li.search-choice{position:relative;margin:3px 0 3px 5px;padding:3px 20px 3px 5px;border:1px solid #aaa;border-radius:3px;background-color:#e4e4e4;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#f4f4f4),color-stop(50%,#f0f0f0),color-stop(52%,#e8e8e8),color-stop(100%,#eee));background-image:-webkit-linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-moz-linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-o-linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:linear-gradient(#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-clip:padding-box;box-shadow:0 0 2px #fff inset,0 1px 0 rgba(0,0,0,.05);color:#333;line-height:13px;cursor:default}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close{position:absolute;top:4px;right:3px;display:block;width:12px;height:12px;background:url(../images/chosen-sprite.png) -42px 1px no-repeat;font-size:1px}.chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover{background-position:-42px -10px}.chosen-container-multi .chosen-choices li.search-choice-disabled{padding-right:5px;border:1px solid #ccc;background-color:#e4e4e4;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#f4f4f4),color-stop(50%,#f0f0f0),color-stop(52%,#e8e8e8),color-stop(100%,#eee));background-image:-webkit-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-moz-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:-o-linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);background-image:linear-gradient(top,#f4f4f4 20%,#f0f0f0 50%,#e8e8e8 52%,#eee 100%);color:#666}.chosen-container-multi .chosen-choices li.search-choice-focus{background:#d4d4d4}.chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close{background-position:-42px -10px}.chosen-container-multi .chosen-results{margin:0;padding:0}.chosen-container-multi .chosen-drop .result-selected{display:list-item;color:#ccc;cursor:default}.chosen-container-active .chosen-single{border:1px solid #5897fb;box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active.chosen-with-drop .chosen-single{border:1px solid #aaa;-moz-border-radius-bottomright:0;border-bottom-right-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0;background-image:-webkit-gradient(linear,50% 0,50% 100%,color-stop(20%,#eee),color-stop(80%,#fff));background-image:-webkit-linear-gradient(#eee 20%,#fff 80%);background-image:-moz-linear-gradient(#eee 20%,#fff 80%);background-image:-o-linear-gradient(#eee 20%,#fff 80%);background-image:linear-gradient(#eee 20%,#fff 80%);box-shadow:0 1px 0 #fff inset}.chosen-container-active.chosen-with-drop .chosen-single div{border-left:0;background:0 0}.chosen-container-active.chosen-with-drop .chosen-single div b{background-position:-18px 2px}.chosen-container-active .chosen-choices{border:1px solid #5897fb;box-shadow:0 0 5px rgba(0,0,0,.3)}.chosen-container-active .chosen-choices li.search-field input[type=text]{color:#111!important}.chosen-disabled{opacity:.5!important;cursor:default}.chosen-disabled .chosen-choices .search-choice .search-choice-close,.chosen-disabled .chosen-single{cursor:default}.chosen-rtl{text-align:right}.chosen-rtl .chosen-single{overflow:visible;padding:0 8px 0 0}.chosen-rtl .chosen-single span{margin-right:0;margin-left:26px;direction:rtl}.chosen-rtl .chosen-single-with-deselect span{margin-left:38px}.chosen-rtl .chosen-single div{right:auto;left:3px}.chosen-rtl .chosen-single abbr{right:auto;left:26px}.chosen-rtl .chosen-choices li{float:right}.chosen-rtl .chosen-choices li.search-field input[type=text]{direction:rtl}.chosen-rtl .chosen-choices li.search-choice{margin:3px 5px 3px 0;padding:3px 5px 3px 19px}.chosen-rtl .chosen-choices li.search-choice .search-choice-close{right:auto;left:4px}.chosen-rtl .chosen-drop,.chosen-rtl.chosen-container-single-nosearch .chosen-search{left:9999px}.chosen-rtl.chosen-container-single .chosen-results{margin:0 0 4px 4px;padding:0 4px 0 0}.chosen-rtl .chosen-results li.group-option{padding-right:15px;padding-left:0}.chosen-rtl.chosen-container-active.chosen-with-drop .chosen-single div{border-right:0}.chosen-rtl .chosen-search input[type=text]{padding:4px 5px 4px 20px;background:#fff url(../images/chosen-sprite.png) no-repeat -30px -20px;background:url(../images/chosen-sprite.png) no-repeat -30px -20px;direction:rtl}.chosen-rtl.chosen-container-single .chosen-single div b{background-position:6px 2px}.chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b{background-position:-12px 2px}@media only screen and (-webkit-min-device-pixel-ratio:2),only screen and (min-resolution:144dpi){.chosen-container .chosen-results-scroll-down span,.chosen-container .chosen-results-scroll-up span,.chosen-container-multi .chosen-choices .search-choice .search-choice-close,.chosen-container-single .chosen-search input[type=text],.chosen-container-single .chosen-single abbr,.chosen-container-single .chosen-single div b,.chosen-rtl .chosen-search input[type=text]{background-image:url(../images/chosen-sprite@2x.png)!important;background-size:52px 37px!important;background-repeat:no-repeat!important}}
assets/css/chosen.less ADDED
@@ -0,0 +1,435 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ Chosen, a Select Box Enhancer for jQuery and Prototype
3
+ by Patrick Filler for Harvest, http://getharvest.com
4
+
5
+ Version 1.1.0
6
+ Full source at https://github.com/harvesthq/chosen
7
+ Copyright (c) 2011 Harvest http://getharvest.com
8
+
9
+ MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
10
+ This file is generated by `grunt build`, do not edit it by hand.
11
+ */
12
+
13
+ /* @group Base */
14
+ .chosen-container {
15
+ position: relative;
16
+ display: inline-block;
17
+ vertical-align: middle;
18
+ font-size: 13px;
19
+ zoom: 1;
20
+ *display: inline;
21
+ -webkit-user-select: none;
22
+ -moz-user-select: none;
23
+ user-select: none;
24
+ }
25
+ .chosen-container .chosen-drop {
26
+ position: absolute;
27
+ top: 100%;
28
+ left: -9999px;
29
+ z-index: 1010;
30
+ -webkit-box-sizing: border-box;
31
+ -moz-box-sizing: border-box;
32
+ box-sizing: border-box;
33
+ width: 100%;
34
+ border: 1px solid #aaa;
35
+ border-top: 0;
36
+ background: #fff;
37
+ box-shadow: 0 4px 5px rgba(0, 0, 0, 0.15);
38
+ }
39
+ .chosen-container.chosen-with-drop .chosen-drop {
40
+ left: 0;
41
+ }
42
+ .chosen-container a {
43
+ cursor: pointer;
44
+ }
45
+
46
+ /* @end */
47
+ /* @group Single Chosen */
48
+ .chosen-container-single .chosen-single {
49
+ position: relative;
50
+ display: block;
51
+ overflow: hidden;
52
+ padding: 0 0 0 8px;
53
+ height: 23px;
54
+ border: 1px solid #aaa;
55
+ border-radius: 5px;
56
+ background-color: #fff;
57
+ background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #ffffff), color-stop(50%, #f6f6f6), color-stop(52%, #eeeeee), color-stop(100%, #f4f4f4));
58
+ background: -webkit-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
59
+ background: -moz-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
60
+ background: -o-linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
61
+ background: linear-gradient(top, #ffffff 20%, #f6f6f6 50%, #eeeeee 52%, #f4f4f4 100%);
62
+ background-clip: padding-box;
63
+ box-shadow: 0 0 3px white inset, 0 1px 1px rgba(0, 0, 0, 0.1);
64
+ color: #444;
65
+ text-decoration: none;
66
+ white-space: nowrap;
67
+ line-height: 24px;
68
+ }
69
+ .chosen-container-single .chosen-default {
70
+ color: #999;
71
+ }
72
+ .chosen-container-single .chosen-single span {
73
+ display: block;
74
+ overflow: hidden;
75
+ margin-right: 26px;
76
+ text-overflow: ellipsis;
77
+ white-space: nowrap;
78
+ }
79
+ .chosen-container-single .chosen-single-with-deselect span {
80
+ margin-right: 38px;
81
+ }
82
+ .chosen-container-single .chosen-single abbr {
83
+ position: absolute;
84
+ top: 6px;
85
+ right: 26px;
86
+ display: block;
87
+ width: 12px;
88
+ height: 12px;
89
+ background: url('../images/chosen-sprite.png') -42px 1px no-repeat;
90
+ font-size: 1px;
91
+ }
92
+ .chosen-container-single .chosen-single abbr:hover {
93
+ background-position: -42px -10px;
94
+ }
95
+ .chosen-container-single.chosen-disabled .chosen-single abbr:hover {
96
+ background-position: -42px -10px;
97
+ }
98
+ .chosen-container-single .chosen-single div {
99
+ position: absolute;
100
+ top: 0;
101
+ right: 0;
102
+ display: block;
103
+ width: 18px;
104
+ height: 100%;
105
+ }
106
+ .chosen-container-single .chosen-single div b {
107
+ display: block;
108
+ width: 100%;
109
+ height: 100%;
110
+ background: url('../images/chosen-sprite.png') no-repeat 0px 2px;
111
+ }
112
+ .chosen-container-single .chosen-search {
113
+ position: relative;
114
+ z-index: 1010;
115
+ margin: 0;
116
+ padding: 3px 4px;
117
+ white-space: nowrap;
118
+ }
119
+ .chosen-container-single .chosen-search input[type="text"] {
120
+ -webkit-box-sizing: border-box;
121
+ -moz-box-sizing: border-box;
122
+ box-sizing: border-box;
123
+ margin: 1px 0;
124
+ padding: 4px 20px 4px 5px;
125
+ width: 100%;
126
+ height: auto;
127
+ outline: 0;
128
+ border: 1px solid #aaa;
129
+ background: white url('../images/chosen-sprite.png') no-repeat 100% -20px;
130
+ background: url('../images/chosen-sprite.png') no-repeat 100% -20px;
131
+ font-size: 1em;
132
+ font-family: sans-serif;
133
+ line-height: normal;
134
+ border-radius: 0;
135
+ }
136
+ .chosen-container-single .chosen-drop {
137
+ margin-top: -1px;
138
+ border-radius: 0 0 4px 4px;
139
+ background-clip: padding-box;
140
+ }
141
+ .chosen-container-single.chosen-container-single-nosearch .chosen-search {
142
+ position: absolute;
143
+ left: -9999px;
144
+ }
145
+
146
+ /* @end */
147
+ /* @group Results */
148
+ .chosen-container .chosen-results {
149
+ position: relative;
150
+ overflow-x: hidden;
151
+ overflow-y: auto;
152
+ margin: 0 4px 4px 0;
153
+ padding: 0 0 0 4px;
154
+ max-height: 240px;
155
+ -webkit-overflow-scrolling: touch;
156
+ }
157
+ .chosen-container .chosen-results li {
158
+ display: none;
159
+ margin: 0;
160
+ padding: 5px 6px;
161
+ list-style: none;
162
+ line-height: 15px;
163
+ -webkit-touch-callout: none;
164
+ }
165
+ .chosen-container .chosen-results li.active-result {
166
+ display: list-item;
167
+ cursor: pointer;
168
+ }
169
+ .chosen-container .chosen-results li.disabled-result {
170
+ display: list-item;
171
+ color: #ccc;
172
+ cursor: default;
173
+ }
174
+ .chosen-container .chosen-results li.highlighted {
175
+ background-color: #3875d7;
176
+ background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc));
177
+ background-image: -webkit-linear-gradient(#3875d7 20%, #2a62bc 90%);
178
+ background-image: -moz-linear-gradient(#3875d7 20%, #2a62bc 90%);
179
+ background-image: -o-linear-gradient(#3875d7 20%, #2a62bc 90%);
180
+ background-image: linear-gradient(#3875d7 20%, #2a62bc 90%);
181
+ color: #fff;
182
+ }
183
+ .chosen-container .chosen-results li.no-results {
184
+ display: list-item;
185
+ background: #f4f4f4;
186
+ }
187
+ .chosen-container .chosen-results li.group-result {
188
+ display: list-item;
189
+ font-weight: bold;
190
+ cursor: default;
191
+ }
192
+ .chosen-container .chosen-results li.group-option {
193
+ padding-left: 15px;
194
+ }
195
+ .chosen-container .chosen-results li em {
196
+ font-style: normal;
197
+ text-decoration: underline;
198
+ }
199
+
200
+ /* @end */
201
+ /* @group Multi Chosen */
202
+ .chosen-container-multi .chosen-choices {
203
+ position: relative;
204
+ overflow: hidden;
205
+ -webkit-box-sizing: border-box;
206
+ -moz-box-sizing: border-box;
207
+ box-sizing: border-box;
208
+ margin: 0;
209
+ padding: 0;
210
+ width: 100%;
211
+ height: auto !important;
212
+ height: 1%;
213
+ border: 1px solid #aaa;
214
+ background-color: #fff;
215
+ background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff));
216
+ background-image: -webkit-linear-gradient(#eeeeee 1%, #ffffff 15%);
217
+ background-image: -moz-linear-gradient(#eeeeee 1%, #ffffff 15%);
218
+ background-image: -o-linear-gradient(#eeeeee 1%, #ffffff 15%);
219
+ background-image: linear-gradient(#eeeeee 1%, #ffffff 15%);
220
+ cursor: text;
221
+ }
222
+ .chosen-container-multi .chosen-choices li {
223
+ float: left;
224
+ list-style: none;
225
+ }
226
+ .chosen-container-multi .chosen-choices li.search-field {
227
+ margin: 0;
228
+ padding: 0;
229
+ white-space: nowrap;
230
+ }
231
+ .chosen-container-multi .chosen-choices li.search-field input[type="text"] {
232
+ margin: 1px 0;
233
+ padding: 5px;
234
+ height: auto;
235
+ outline: 0;
236
+ border: 0 !important;
237
+ background: transparent !important;
238
+ box-shadow: none;
239
+ color: #666;
240
+ font-size: 100%;
241
+ font-family: sans-serif;
242
+ line-height: normal;
243
+ border-radius: 0;
244
+ }
245
+ .chosen-container-multi .chosen-choices li.search-field .default {
246
+ color: #999;
247
+ }
248
+ .chosen-container-multi .chosen-choices li.search-choice {
249
+ position: relative;
250
+ margin: 3px 0 3px 5px;
251
+ padding: 3px 20px 3px 5px;
252
+ border: 1px solid #aaa;
253
+ border-radius: 3px;
254
+ background-color: #e4e4e4;
255
+ background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
256
+ background-image: -webkit-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
257
+ background-image: -moz-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
258
+ background-image: -o-linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
259
+ background-image: linear-gradient(#f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
260
+ background-clip: padding-box;
261
+ box-shadow: 0 0 2px white inset, 0 1px 0 rgba(0, 0, 0, 0.05);
262
+ color: #333;
263
+ line-height: 13px;
264
+ cursor: default;
265
+ }
266
+ .chosen-container-multi .chosen-choices li.search-choice .search-choice-close {
267
+ position: absolute;
268
+ top: 4px;
269
+ right: 3px;
270
+ display: block;
271
+ width: 12px;
272
+ height: 12px;
273
+ background: url('../images/chosen-sprite.png') -42px 1px no-repeat;
274
+ font-size: 1px;
275
+ }
276
+ .chosen-container-multi .chosen-choices li.search-choice .search-choice-close:hover {
277
+ background-position: -42px -10px;
278
+ }
279
+ .chosen-container-multi .chosen-choices li.search-choice-disabled {
280
+ padding-right: 5px;
281
+ border: 1px solid #ccc;
282
+ background-color: #e4e4e4;
283
+ background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee));
284
+ background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
285
+ background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
286
+ background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
287
+ background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%);
288
+ color: #666;
289
+ }
290
+ .chosen-container-multi .chosen-choices li.search-choice-focus {
291
+ background: #d4d4d4;
292
+ }
293
+ .chosen-container-multi .chosen-choices li.search-choice-focus .search-choice-close {
294
+ background-position: -42px -10px;
295
+ }
296
+ .chosen-container-multi .chosen-results {
297
+ margin: 0;
298
+ padding: 0;
299
+ }
300
+ .chosen-container-multi .chosen-drop .result-selected {
301
+ display: list-item;
302
+ color: #ccc;
303
+ cursor: default;
304
+ }
305
+
306
+ /* @end */
307
+ /* @group Active */
308
+ .chosen-container-active .chosen-single {
309
+ border: 1px solid #5897fb;
310
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
311
+ }
312
+ .chosen-container-active.chosen-with-drop .chosen-single {
313
+ border: 1px solid #aaa;
314
+ -moz-border-radius-bottomright: 0;
315
+ border-bottom-right-radius: 0;
316
+ -moz-border-radius-bottomleft: 0;
317
+ border-bottom-left-radius: 0;
318
+ background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(20%, #eeeeee), color-stop(80%, #ffffff));
319
+ background-image: -webkit-linear-gradient(#eeeeee 20%, #ffffff 80%);
320
+ background-image: -moz-linear-gradient(#eeeeee 20%, #ffffff 80%);
321
+ background-image: -o-linear-gradient(#eeeeee 20%, #ffffff 80%);
322
+ background-image: linear-gradient(#eeeeee 20%, #ffffff 80%);
323
+ box-shadow: 0 1px 0 #fff inset;
324
+ }
325
+ .chosen-container-active.chosen-with-drop .chosen-single div {
326
+ border-left: none;
327
+ background: transparent;
328
+ }
329
+ .chosen-container-active.chosen-with-drop .chosen-single div b {
330
+ background-position: -18px 2px;
331
+ }
332
+ .chosen-container-active .chosen-choices {
333
+ border: 1px solid #5897fb;
334
+ box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
335
+ }
336
+ .chosen-container-active .chosen-choices li.search-field input[type="text"] {
337
+ color: #111 !important;
338
+ }
339
+
340
+ /* @end */
341
+ /* @group Disabled Support */
342
+ .chosen-disabled {
343
+ opacity: 0.5 !important;
344
+ cursor: default;
345
+ }
346
+ .chosen-disabled .chosen-single {
347
+ cursor: default;
348
+ }
349
+ .chosen-disabled .chosen-choices .search-choice .search-choice-close {
350
+ cursor: default;
351
+ }
352
+
353
+ /* @end */
354
+ /* @group Right to Left */
355
+ .chosen-rtl {
356
+ text-align: right;
357
+ }
358
+ .chosen-rtl .chosen-single {
359
+ overflow: visible;
360
+ padding: 0 8px 0 0;
361
+ }
362
+ .chosen-rtl .chosen-single span {
363
+ margin-right: 0;
364
+ margin-left: 26px;
365
+ direction: rtl;
366
+ }
367
+ .chosen-rtl .chosen-single-with-deselect span {
368
+ margin-left: 38px;
369
+ }
370
+ .chosen-rtl .chosen-single div {
371
+ right: auto;
372
+ left: 3px;
373
+ }
374
+ .chosen-rtl .chosen-single abbr {
375
+ right: auto;
376
+ left: 26px;
377
+ }
378
+ .chosen-rtl .chosen-choices li {
379
+ float: right;
380
+ }
381
+ .chosen-rtl .chosen-choices li.search-field input[type="text"] {
382
+ direction: rtl;
383
+ }
384
+ .chosen-rtl .chosen-choices li.search-choice {
385
+ margin: 3px 5px 3px 0;
386
+ padding: 3px 5px 3px 19px;
387
+ }
388
+ .chosen-rtl .chosen-choices li.search-choice .search-choice-close {
389
+ right: auto;
390
+ left: 4px;
391
+ }
392
+ .chosen-rtl.chosen-container-single-nosearch .chosen-search,
393
+ .chosen-rtl .chosen-drop {
394
+ left: 9999px;
395
+ }
396
+ .chosen-rtl.chosen-container-single .chosen-results {
397
+ margin: 0 0 4px 4px;
398
+ padding: 0 4px 0 0;
399
+ }
400
+ .chosen-rtl .chosen-results li.group-option {
401
+ padding-right: 15px;
402
+ padding-left: 0;
403
+ }
404
+ .chosen-rtl.chosen-container-active.chosen-with-drop .chosen-single div {
405
+ border-right: none;
406
+ }
407
+ .chosen-rtl .chosen-search input[type="text"] {
408
+ padding: 4px 5px 4px 20px;
409
+ background: white url('../images/chosen-sprite.png') no-repeat -30px -20px;
410
+ background: url('../images/chosen-sprite.png') no-repeat -30px -20px;
411
+ direction: rtl;
412
+ }
413
+ .chosen-rtl.chosen-container-single .chosen-single div b {
414
+ background-position: 6px 2px;
415
+ }
416
+ .chosen-rtl.chosen-container-single.chosen-with-drop .chosen-single div b {
417
+ background-position: -12px 2px;
418
+ }
419
+
420
+ /* @end */
421
+ /* @group Retina compatibility */
422
+ @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-resolution: 144dpi) {
423
+ .chosen-rtl .chosen-search input[type="text"],
424
+ .chosen-container-single .chosen-single abbr,
425
+ .chosen-container-single .chosen-single div b,
426
+ .chosen-container-single .chosen-search input[type="text"],
427
+ .chosen-container-multi .chosen-choices .search-choice .search-choice-close,
428
+ .chosen-container .chosen-results-scroll-down span,
429
+ .chosen-container .chosen-results-scroll-up span {
430
+ background-image: url('../images/chosen-sprite@2x.png') !important;
431
+ background-size: 52px 37px !important;
432
+ background-repeat: no-repeat !important;
433
+ }
434
+ }
435
+ /* @end */
assets/css/frontend.css CHANGED
@@ -1 +1 @@
1
- .clearfix{zoom:1}.clearfix:after,.clearfix:before{content:"";display:table}.clearfix:after{clear:both}@font-face{font-family:job-manager;src:url(../font/job-manager.eot?4963673);src:url(../font/job-manager.eot?4963673#iefix) format('embedded-opentype'),url(../font/job-manager.woff?4963673) format('woff'),url(../font/job-manager.ttf?4963673) format('truetype'),url(../font/job-manager.svg?4963673#job-manager) format('svg');font-weight:400;font-style:normal}@font-face{font-family:jm-logo;src:url(../font/jm-logo/jm.eot?ycsbky);src:url(../font/jm-logo/jm.eot?#iefixycsbky) format('embedded-opentype'),url(../font/jm-logo/jm.woff?ycsbky) format('woff'),url(../font/jm-logo/jm.ttf?ycsbky) format('truetype'),url(../font/jm-logo/jm.svg?ycsbky#icomoon) format('svg');font-weight:400;font-style:normal}.jm-icon{font-family:job-manager!important;font-style:normal;font-weight:400;speak:none;display:inline-block;text-decoration:inherit;width:1em;text-align:center;font-variant:normal;text-transform:none;line-height:1em}.rp4wp-related-job_listing>ul,ul.job_listings{padding:0;margin:0;border-top:1px solid #eee}.rp4wp-related-job_listing>ul.loading,ul.job_listings.loading{min-height:96px;border-bottom:1px solid #eee;background:url(../images/ajax-loader.gif) no-repeat center 32px}.rp4wp-related-job_listing>ul li.job_listing,.rp4wp-related-job_listing>ul li.no_job_listings_found,ul.job_listings li.job_listing,ul.job_listings li.no_job_listings_found{list-style:none outside;padding:0;margin:0;border-bottom:1px solid #eee}.rp4wp-related-job_listing>ul li.job_listing.job_position_filled a,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_filled a,ul.job_listings li.job_listing.job_position_filled a,ul.job_listings li.no_job_listings_found.job_position_filled a{opacity:.25}.rp4wp-related-job_listing>ul li.job_listing.no_job_listings_found,.rp4wp-related-job_listing>ul li.no_job_listings_found.no_job_listings_found,ul.job_listings li.job_listing.no_job_listings_found,ul.job_listings li.no_job_listings_found.no_job_listings_found{padding:1em;border-bottom:1px solid #eee}.rp4wp-related-job_listing>ul li.job_listing a,.rp4wp-related-job_listing>ul li.no_job_listings_found a,ul.job_listings li.job_listing a,ul.job_listings li.no_job_listings_found a{display:block;padding:1em 1em 1em 2em;border:0;overflow:hidden;zoom:1;position:relative;line-height:1.5em;text-decoration:none}.rp4wp-related-job_listing>ul li.job_listing a:focus,.rp4wp-related-job_listing>ul li.job_listing a:hover,.rp4wp-related-job_listing>ul li.no_job_listings_found a:focus,.rp4wp-related-job_listing>ul li.no_job_listings_found a:hover,ul.job_listings li.job_listing a:focus,ul.job_listings li.job_listing a:hover,ul.job_listings li.no_job_listings_found a:focus,ul.job_listings li.no_job_listings_found a:hover{background-color:#fcfcfc}.rp4wp-related-job_listing>ul li.job_listing a img.company_logo,.rp4wp-related-job_listing>ul li.no_job_listings_found a img.company_logo,ul.job_listings li.job_listing a img.company_logo,ul.job_listings li.no_job_listings_found a img.company_logo{width:42px;height:42px;position:absolute;left:1em;float:left;margin-right:1em;vertical-align:middle;box-shadow:none}.rp4wp-related-job_listing>ul li.job_listing a div.location,.rp4wp-related-job_listing>ul li.job_listing a div.position,.rp4wp-related-job_listing>ul li.job_listing a ul.meta,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.location,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position,.rp4wp-related-job_listing>ul li.no_job_listings_found a ul.meta,ul.job_listings li.job_listing a div.location,ul.job_listings li.job_listing a div.position,ul.job_listings li.job_listing a ul.meta,ul.job_listings li.no_job_listings_found a div.location,ul.job_listings li.no_job_listings_found a div.position,ul.job_listings li.no_job_listings_found a ul.meta{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.rp4wp-related-job_listing>ul li.job_listing a div.position,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position,ul.job_listings li.job_listing a div.position,ul.job_listings li.no_job_listings_found a div.position{float:left;width:55%;padding:0 0 0 42px;line-height:1.5em}.rp4wp-related-job_listing>ul li.job_listing a div.position h3,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position h3,ul.job_listings li.job_listing a div.position h3,ul.job_listings li.no_job_listings_found a div.position h3{margin:0;padding:0;line-height:inherit;font-size:inherit}.rp4wp-related-job_listing>ul li.job_listing a div.position .company,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position .company,ul.job_listings li.job_listing a div.position .company,ul.job_listings li.no_job_listings_found a div.position .company{color:#999}.rp4wp-related-job_listing>ul li.job_listing a div.position .company .tagline,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position .company .tagline,ul.job_listings li.job_listing a div.position .company .tagline,ul.job_listings li.no_job_listings_found a div.position .company .tagline{margin-left:.5em}.rp4wp-related-job_listing>ul li.job_listing a div.location,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.location,ul.job_listings li.job_listing a div.location,ul.job_listings li.no_job_listings_found a div.location{float:left;text-align:left;width:25%;padding:0 0 0 1em;color:#999;line-height:1.5em}.rp4wp-related-job_listing>ul li.job_listing a .meta,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta,ul.job_listings li.job_listing a .meta,ul.job_listings li.no_job_listings_found a .meta{float:right;text-align:right;width:20%;padding:0 0 0 1em;margin:0;line-height:1.5em;color:#999;list-style:none outside}.rp4wp-related-job_listing>ul li.job_listing a .meta li,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta li,ul.job_listings li.job_listing a .meta li,ul.job_listings li.no_job_listings_found a .meta li{list-style:none outside;display:block;margin:0}.rp4wp-related-job_listing>ul li.job_listing a .meta .job-type,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta .job-type,ul.job_listings li.job_listing a .meta .job-type,ul.job_listings li.no_job_listings_found a .meta .job-type{font-weight:700}.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a,ul.job_listings li.job_listing.job_position_featured a,ul.job_listings li.no_job_listings_found.job_position_featured a{background:#fefee5}.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a:focus,.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a:hover,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a:focus,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a:hover,ul.job_listings li.job_listing.job_position_featured a:focus,ul.job_listings li.job_listing.job_position_featured a:hover,ul.job_listings li.no_job_listings_found.job_position_featured a:focus,ul.job_listings li.no_job_listings_found.job_position_featured a:hover{background-color:#fefed8}.widget ul.job_listings li.job_listing a{padding:1em 0}.widget ul.job_listings li.job_listing .image{float:left}.widget ul.job_listings li.job_listing .image img{left:0;position:relative}.widget ul.job_listings li.job_listing .content{overflow:hidden}.widget ul.job_listings li.job_listing .position{float:none;width:auto;padding:0}.widget ul.job_listings li.job_listing ul.meta{float:none;width:auto;padding:0;margin:0;text-align:left}.widget ul.job_listings li.job_listing ul.meta li{float:none;display:inline;padding:0;margin:0 .5em 0 0;font-weight:400}.widget ul.job_listings li.job_listing ul.meta li:after{padding:0 0 0 .5em;content:"\2023"}.widget ul.job_listings li.job_listing ul.meta li:last-child:after{content:''}.job-manager .job-type,.job-types .job-type,.job_listing .job-type{color:#f08d3c}.job-manager .full-time,.job-types .full-time,.job_listing .full-time{color:#90da36}.job-manager .part-time,.job-types .part-time,.job_listing .part-time{color:#f08d3c}.job-manager .temporary,.job-types .temporary,.job_listing .temporary{color:#d93674}.job-manager .freelance,.job-types .freelance,.job_listing .freelance{color:#39c}.job-manager .internship,.job-types .internship,.job_listing .internship{color:#6033cc}@media only screen and (max-width:767px){ul.job_listings li.job_listing a,ul.job_listings li.no_job_listings_found a{padding:1em}ul.job_listings li.job_listing a img.company_logo,ul.job_listings li.no_job_listings_found a img.company_logo{visibility:hidden}ul.job_listings li.job_listing a div.position,ul.job_listings li.no_job_listings_found a div.position{float:left;width:60%;padding:0}ul.job_listings li.job_listing a div.location,ul.job_listings li.no_job_listings_found a div.location{float:right;width:40%;line-height:2em;font-size:.75em;padding:0 0 0 1em;text-align:right}ul.job_listings li.job_listing a .meta,ul.job_listings li.no_job_listings_found a .meta{float:right;width:40%;line-height:2em;font-size:.75em}ul.job_listings li.job_listing a .meta li,ul.job_listings li.no_job_listings_found a .meta li{font-size:1em}}.twenty-eleven ul.job_listings li.job_listing,.twenty-eleven ul.job_listings li.no_job_listings_found{padding:0!important}.display-icon{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0}.job-manager-error,.job-manager-info,.job-manager-message{padding:1em 2em 1em 3.5em!important;margin:0 0 2em!important;position:relative;background-color:#fff;color:#666;border-top:3px solid #999;list-style:none outside!important;width:auto;zoom:1;box-shadow:0 1px 1px rgba(0,0,0,.2)}.job-manager-error:after,.job-manager-error:before,.job-manager-info:after,.job-manager-info:before,.job-manager-message:after,.job-manager-message:before{content:"";display:table}.job-manager-error:after,.job-manager-info:after,.job-manager-message:after{clear:both}.job-manager-error:before,.job-manager-info:before,.job-manager-message:before{content:"";font-family:sans-serif;display:inline-block;position:absolute;top:1em;left:1.5em}.job-manager-error li,.job-manager-info li,.job-manager-message li{list-style:none outside!important;padding-left:0!important;margin-left:0!important}.job-manager-error.job-manager-message,.job-manager-info.job-manager-message,.job-manager-message.job-manager-message{border-top-color:#8fae1b}.job-manager-error.job-manager-message:before,.job-manager-info.job-manager-message:before,.job-manager-message.job-manager-message:before{color:#8fae1b;content:"\2713"}.job-manager-error.job-manager-info,.job-manager-info.job-manager-info,.job-manager-message.job-manager-info{border-top-color:#1e85be}.job-manager-error.job-manager-info:before,.job-manager-info.job-manager-info:before,.job-manager-message.job-manager-info:before{color:#1e85be;content:"i";font-family:Times,Georgia,serif;font-style:italic}.job-manager-error.job-manager-error,.job-manager-info.job-manager-error,.job-manager-message.job-manager-error{border-top-color:#b81c23}.job-manager-error.job-manager-error:before,.job-manager-info.job-manager-error:before,.job-manager-message.job-manager-error:before{color:#b81c23;content:"\00d7";font-weight:700}.job-manager-form fieldset{margin:0 0 1em 0;padding:0 0 1em 0;line-height:2em;border:0;border-bottom:1px solid #eee;zoom:1}.job-manager-form fieldset:after,.job-manager-form fieldset:before{content:"";display:table}.job-manager-form fieldset:after{clear:both}.job-manager-form fieldset label{display:block;margin:0;width:29%;float:left;vertical-align:middle}.job-manager-form fieldset label small{opacity:.75;font-size:.83em}.job-manager-form fieldset div.field{width:70%;float:right;vertical-align:middle}.job-manager-form fieldset .wp-editor-container{border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.job-manager-form fieldset .account-sign-in .button{margin-right:.5em}.job-manager-form fieldset .account-sign-in .button:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;content:'\e808'}.job-manager-form fieldset abbr.required{color:red;font-weight:700;border:0}.job-manager-form fieldset input.input-text,.job-manager-form fieldset select,.job-manager-form fieldset textarea{margin:0;vertical-align:middle;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job-manager-form fieldset small.description{opacity:.75;font-size:.83em;margin:1.2em 0 0 0;display:block;line-height:1.2em}.job-manager-form fieldset .job-manager-uploaded-files{display:table}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file{line-height:2em;font-style:italic;margin-bottom:1em;display:block}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file .job-manager-uploaded-file-preview img{height:64px;margin:0;vertical-align:top}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file .job-manager-uploaded-file-preview a{line-height:64px;display:inline-block;padding:0 0 0 1em}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file .job-manager-uploaded-file-name{display:block}.job-manager-form .submit-job{padding:1em 0}.job-manager-form .job-manager-term-checklist{list-style:none outside;max-height:200px;overflow:auto;margin:0}.job-manager-form .job-manager-term-checklist li{list-style:none outside;margin:0;display:block;float:none}.job-manager-form .job-manager-term-checklist li label{width:auto;float:none}.job-manager-form .job-manager-term-checklist li li{margin:0 0 0 2em}.job-manager-form input[type=submit].disabled,.job-manager-form input[type=submit]:disabled{opacity:.5;cursor:not-allowed}.job-manager-form .spinner{background:url(../../../../../wp-includes/images/spinner.gif) no-repeat;background-size:20px 20px;display:inline-block;visibility:hidden;width:20px;height:20px;margin:0;vertical-align:middle}.job-manager-form .spinner.is-active{visibility:visible}div.job_listings{margin-bottom:1em}div.job_listings ul.job_listings{margin:0}.single_job_listing .company{position:relative;border:1px solid #eee;padding:1em;margin:0 0 2em;display:block;clear:both;min-height:3em;box-shadow:0 1px 1px rgba(0,0,0,.1)}.single_job_listing .company img{width:3em;height:3em;position:absolute;left:1em;float:left;vertical-align:middle;box-shadow:none}.single_job_listing .company .name{margin:0 0 0 3em;padding:0 0 0 1em;line-height:1.5em}.single_job_listing .company .name a{float:right;margin-left:1em}.single_job_listing .company .tagline{display:block;margin:0 0 0 42px;padding:0 0 0 1em;line-height:1.5em;font-style:italic;color:#999}.single_job_listing .company .website:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;content:'\e809'}.single_job_listing .company .company_twitter:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;content:'\e80a'}.single_job_listing .company .company_video{border-top:1px solid #eee;padding:1em 0 0;margin:1em 0 0 0;position:relative;padding-bottom:56.25%;padding-top:30px;height:0;overflow:hidden}.single_job_listing .company .company_video embed,.single_job_listing .company .company_video iframe,.single_job_listing .company .company_video object{position:absolute;top:0;left:0;width:100%;height:100%;margin:0;display:block}.single_job_listing .meta{list-style:none outside;padding:0;margin:0 0 1.5em;overflow:hidden;zoom:1;clear:both}.single_job_listing .meta li{margin:0 1em 0 0;padding:.5em;float:left;line-height:1em;color:#999}.single_job_listing .meta .job-type{color:#fff;background-color:#f08d3c}.single_job_listing .meta .full-time{background-color:#90da36}.single_job_listing .meta .part-time{background-color:#f08d3c}.single_job_listing .meta .temporary{background-color:#d93674}.single_job_listing .meta .freelance{background-color:#39c}.single_job_listing .meta .internship{background-color:#6033cc}.single_job_listing .meta .listing-expired,.single_job_listing .meta .position-filled{color:#b81c23}.single_job_listing .meta .location:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;content:'\e81d'}.single_job_listing .meta .date-posted:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;content:'\e80f'}.single_job_listing .meta .listing-expired:before,.single_job_listing .meta .position-filled:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;content:'\e80e'}.single_job_listing .job_description{margin:0 0 1.5em}.job-manager-application-wrapper{clear:both;border:1px solid #eee;padding:.75em 1em 0;margin:1em 0;line-height:1.5em;display:block;position:relative;box-shadow:0 1px 1px rgba(0,0,0,.1)}.job-manager-application-wrapper .application,.single_job_listing .application{padding:0;margin:0 0 1em;overflow:hidden}.job-manager-application-wrapper .application .application_button,.job-manager-application-wrapper .application .application_details,.single_job_listing .application .application_button,.single_job_listing .application .application_details{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job-manager-application-wrapper .application .application_button,.single_job_listing .application .application_button{text-align:center;font-size:1.1em;line-height:1em;display:inline-block;margin:0 .5em 0 0;padding:.90909091em 2em;outline:0}.job-manager-application-wrapper .application .application_details,.single_job_listing .application .application_details{clear:both;border:1px solid #eee;padding:.75em 1em 0;margin:1em 0;line-height:1.5em;display:block;position:relative;box-shadow:0 1px 1px rgba(0,0,0,.1)}.job-manager-application-wrapper .application .application_details p,.single_job_listing .application .application_details p{margin:0 0 .75em}.job-manager-application-wrapper .application .application_details:before,.single_job_listing .application .application_details:before{content:"";position:absolute;margin:-10px 0 0 0;top:0;left:5em;width:0;height:0;border-left:10px solid transparent;border-right:10px solid transparent;border-bottom:10px solid #eee}.job-manager-application-wrapper .application .application_details:after,.single_job_listing .application .application_details:after{content:"";position:absolute;margin:-9px 0 0 1px;left:5em;top:0;width:0;height:0;border-left:9px solid transparent;border-right:9px solid transparent;border-bottom:9px solid #fff}.job_filters{background:#eee;zoom:1}.job_filters:after,.job_filters:before{content:"";display:table}.job_filters:after{clear:both}.job_filters .search_jobs{padding:1em;zoom:1}.job_filters .search_jobs:after,.job_filters .search_jobs:before{content:"";display:table}.job_filters .search_jobs:after{clear:both}.job_filters .search_jobs div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job_filters .search_jobs div label{display:none}.job_filters .search_jobs div.filter_first,.job_filters .search_jobs div.search_keywords{float:left;padding-right:.5em;width:50%}.job_filters .search_jobs div.filter_last,.job_filters .search_jobs div.search_location{float:right;padding-left:.5em;width:50%}.job_filters .search_jobs div.filter_wide,.job_filters .search_jobs div.search_categories{padding-top:.5em;clear:both;width:100%}.job_filters .search_jobs div .showing_jobs a{padding:.25em}.job_filters .search_jobs div .showing_jobs a.active{background:#ddd;text-decoration:none}.job_filters .search_jobs input,.job_filters .search_jobs select{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%}.job_filters .job_types{list-style:none outside;margin:0;padding:0;font-size:.83em;background:#f9f9f9;border-top:1px solid #e5e5e5;zoom:1}.job_filters .job_types:after,.job_filters .job_types:before{content:"";display:table}.job_filters .job_types:after{clear:both}.job_filters .job_types li{margin:0;padding:.5em 1em;float:left;border-right:1px solid #eee}.job_filters .job_types li:last-child{border-right:0}.job_filters .showing_jobs{padding:.5em 1em;display:none;font-size:.83em;background:#f9f9f9;border-top:1px solid #e5e5e5}.job_filters .showing_jobs a{float:right;padding-left:10px;border:0}div.job_listings .job-manager-pagination{text-align:center;display:block;padding:1em 0 1em 0;border-bottom:1px solid #eee;line-height:1}div.job_listings .load_previous{border-top:1px solid #eee}div.job_listings .load_more_jobs+ul.job_listings{border-top:0}div.job_listings .load_more_jobs{text-align:center;display:block;padding:1em 1em 1em 2em;border-bottom:1px solid #eee;font-weight:700}div.job_listings .load_more_jobs.loading{background:url(../images/ajax-loader.gif) no-repeat center}div.job_listings .load_more_jobs.loading strong{visibility:hidden}div.job_listings .load_more_jobs:focus,div.job_listings .load_more_jobs:hover{background-color:#fcfcfc;border-bottom:1px solid #eee}.job_listing_preview{padding:0 1em 1em;border:5px solid #eee}.single-job_listing .entry-header .attachment-post-thumbnail,.single-job_listing .job_listing.has-post-thumbnail .post-thumbnail{display:none}.entry-content .job_listing_preview_title,.job_listing_preview_title{padding:.5em 1em;vertical-align:middle;position:relative;background:#eee}.entry-content .job_listing_preview_title h2,.job_listing_preview_title h2{margin:0;clear:none}.entry-content .job_listing_preview_title .button,.job_listing_preview_title .button{float:right;margin-left:.25em}.job_summary_shortcode{border:1px solid #ccc;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;text-align:center;position:relative;box-shadow:0 2px 4px rgba(0,0,0,.1),inset 0 1px 0 rgba(255,255,255,.4);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job_summary_shortcode.aligncenter{display:block;margin:2em auto 2em}.job_summary_shortcode.alignleft{float:left;margin:0 2em 2em 0}.job_summary_shortcode.alignright{float:right;margin:0 0 2em 2em}.job_summary_shortcode a{text-decoration:none;color:inherit}.job_summary_shortcode img{margin:0;padding:0;display:block;width:100%;-moz-border-radius:0;-webkit-border-radius:0;-moz-border-top-left-radius:3px;-moz-border-top-right-radius:3px;-webkit-border-top-left-radius:3px;-webkit-border-top-right-radius:3px;border-radius:0;border-top-left-radius:3px;border-top-right-radius:3px;box-shadow:inset 0 1px 0 rgba(255,255,255,.4)}.job_summary_shortcode .job_summary_content{padding:0 1em}.job_summary_shortcode .meta{font-style:italic;color:#777}.job_summary_shortcode .job-type{-moz-border-radius:1em;-webkit-border-radius:1em;border-radius:1em;color:#fff;text-shadow:0 1px 0 rgba(255,255,255,.5);box-shadow:0 2px 4px rgba(0,0,0,.1),inset 0 1px 0 rgba(255,255,255,.4);position:absolute;top:0;right:0;padding:.5em;height:1em;width:auto;min-width:1em;font-size:1em;text-align:center;vertical-align:middle;line-height:1em;margin:-.5em -.5em 0 0}.job_summary_shortcode .job-type.full-time{background-color:#90da36}.job_summary_shortcode .job-type.part-time{background-color:#f08d3c}.job_summary_shortcode .job-type.temporary{background-color:#d93674}.job_summary_shortcode .job-type.freelance{background-color:#39c}.job_summary_shortcode .job-type.internship{background-color:#6033cc}#job-manager-job-dashboard .account-sign-in .button{margin-right:.5em}#job-manager-job-dashboard .account-sign-in .button:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;content:'\e808'}#job-manager-job-dashboard table ul.job-dashboard-actions{margin:0;padding:0;visibility:hidden;font-size:.92em}#job-manager-job-dashboard table ul.job-dashboard-actions li{float:none;display:inline;padding:0;margin:0 .5em 0 0;font-weight:400;list-style:none outside}#job-manager-job-dashboard table ul.job-dashboard-actions li:after{padding:0 0 0 .5em;content:"\2023"}#job-manager-job-dashboard table ul.job-dashboard-actions li:last-child:after{content:''}#job-manager-job-dashboard table ul.job-dashboard-actions li .job-dashboard-action-delete{color:red}#job-manager-job-dashboard table tr:focus ul.job-dashboard-actions,#job-manager-job-dashboard table tr:hover ul.job-dashboard-actions{visibility:visible}#job-manager-job-dashboard table td,#job-manager-job-dashboard table th{padding:.5em 1em .5em 0}#job-manager-job-dashboard table .job_title small{color:#999}#job-manager-job-dashboard table .featured-job-icon: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}#job-manager-job-dashboard table .applications,#job-manager-job-dashboard table .expires,#job-manager-job-dashboard table .filled{text-align:center}#content nav.job-manager-pagination,nav.job-manager-pagination{text-align:center}#content nav.job-manager-pagination ul,nav.job-manager-pagination ul{display:inline-block;white-space:nowrap;padding:0;clear:both;border-left:1px solid #eee;margin:1px}#content nav.job-manager-pagination ul li,nav.job-manager-pagination ul li{border-right:1px solid #eee;border-top:1px solid #eee;border-bottom:1px solid #eee;padding:0;margin:0;float:left;display:inline;overflow:hidden}#content nav.job-manager-pagination ul li a,#content nav.job-manager-pagination ul li span,nav.job-manager-pagination ul li a,nav.job-manager-pagination ul li span{margin:0;text-decoration:none;padding:0;line-height:1em;font-size:1em;font-weight:400;padding:.5em;min-width:1em;display:block;border:0}#content nav.job-manager-pagination ul li a:focus,#content nav.job-manager-pagination ul li a:hover,#content nav.job-manager-pagination ul li span.current,nav.job-manager-pagination ul li a:focus,nav.job-manager-pagination ul li a:hover,nav.job-manager-pagination ul li span.current{background:#eee;color:#888}.chosen-container{width:100%!important}.twenty-ten .chosen-choices,.twenty-ten .job_types{margin:0!important}.rtl .job-manager-form label{float:right}.rtl .job-manager-form div.field{float:left}.rtl .entry-content .job_listing_preview_title .button,.rtl .job_listing_preview_title .button{float:left}.rtl .single_job_listing .meta li{float:right;margin:0 0 0 1em}
1
+ .clearfix{zoom:1}.clearfix:after,.clearfix:before{content:"";display:table}.clearfix:after{clear:both}@font-face{font-family:job-manager;src:url(../font/job-manager.eot?4963673);src:url(../font/job-manager.eot?4963673#iefix) format('embedded-opentype'),url(../font/job-manager.woff?4963673) format('woff'),url(../font/job-manager.ttf?4963673) format('truetype'),url(../font/job-manager.svg?4963673#job-manager) format('svg');font-weight:400;font-style:normal}@font-face{font-family:jm-logo;src:url(../font/jm-logo/jm.eot?ycsbky);src:url(../font/jm-logo/jm.eot?#iefixycsbky) format('embedded-opentype'),url(../font/jm-logo/jm.woff?ycsbky) format('woff'),url(../font/jm-logo/jm.ttf?ycsbky) format('truetype'),url(../font/jm-logo/jm.svg?ycsbky#icomoon) format('svg');font-weight:400;font-style:normal}.jm-icon{font-family:job-manager!important;font-style:normal;font-weight:400;speak:none;display:inline-block;text-decoration:inherit;width:1em;text-align:center;font-variant:normal;text-transform:none;line-height:1em}.display-icon{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline}.job-manager-error,.job-manager-info,.job-manager-message{padding:1em 2em 1em 3.5em!important;margin:0 0 2em!important;position:relative;background-color:#fff;color:#666;border-top:3px solid #999;list-style:none outside!important;width:auto;zoom:1;box-shadow:0 1px 1px rgba(0,0,0,.2)}.job-manager-error:after,.job-manager-error:before,.job-manager-info:after,.job-manager-info:before,.job-manager-message:after,.job-manager-message:before{content:"";display:table}.job-manager-error:after,.job-manager-info:after,.job-manager-message:after{clear:both}.job-manager-error:before,.job-manager-info:before,.job-manager-message:before{content:"";font-family:sans-serif;display:inline-block;position:absolute;top:1em;left:1.5em}.job-manager-error li,.job-manager-info li,.job-manager-message li{list-style:none outside!important;padding-left:0!important;margin-left:0!important}.job-manager-error.job-manager-message,.job-manager-info.job-manager-message,.job-manager-message.job-manager-message{border-top-color:#8fae1b}.job-manager-error.job-manager-message:before,.job-manager-info.job-manager-message:before,.job-manager-message.job-manager-message:before{color:#8fae1b;content:"\2713"}.job-manager-error.job-manager-info,.job-manager-info.job-manager-info,.job-manager-message.job-manager-info{border-top-color:#1e85be}.job-manager-error.job-manager-info:before,.job-manager-info.job-manager-info:before,.job-manager-message.job-manager-info:before{color:#1e85be;content:"i";font-family:Times,Georgia,serif;font-style:italic}.job-manager-error.job-manager-error,.job-manager-info.job-manager-error,.job-manager-message.job-manager-error{border-top-color:#b81c23}.job-manager-error.job-manager-error:before,.job-manager-info.job-manager-error:before,.job-manager-message.job-manager-error:before{color:#b81c23;content:"\00d7";font-weight:700}.job-manager-form fieldset{margin:0 0 1em;padding:0 0 1em;line-height:2em;border:0;border-bottom:1px solid #eee;zoom:1}.job-manager-form fieldset:after,.job-manager-form fieldset:before{content:"";display:table}.job-manager-form fieldset:after{clear:both}.job-manager-form fieldset label{display:block;margin:0;width:29%;float:left;vertical-align:middle}.job-manager-form fieldset label small{opacity:.75;font-size:.83em}.job-manager-form fieldset div.field{width:70%;float:right;vertical-align:middle}.job-manager-form fieldset .wp-editor-container{border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.job-manager-form fieldset .account-sign-in .button{margin-right:.5em}.job-manager-form fieldset .account-sign-in .button:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e808'}.job-manager-form fieldset abbr.required{color:red;font-weight:700;border:0}.job-manager-form fieldset input.input-text,.job-manager-form fieldset select,.job-manager-form fieldset textarea{margin:0;vertical-align:middle;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job-manager-form fieldset small.description{opacity:.75;font-size:.83em;margin:1.2em 0 0;display:block;line-height:1.2em}.job-manager-form fieldset .job-manager-uploaded-files{display:table}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file{line-height:2em;font-style:italic;margin-bottom:1em;display:block}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file .job-manager-uploaded-file-preview img{height:64px;margin:0;vertical-align:top}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file .job-manager-uploaded-file-preview a{line-height:64px;display:inline-block;padding:0 0 0 1em}.job-manager-form fieldset .job-manager-uploaded-files .job-manager-uploaded-file .job-manager-uploaded-file-name{display:block}.job-manager-form .submit-job{padding:1em 0}.job-manager-form .job-manager-term-checklist{list-style:none outside;max-height:200px;overflow:auto;margin:0}.job-manager-form .job-manager-term-checklist li{list-style:none outside;margin:0;display:block;float:none}.job-manager-form .job-manager-term-checklist li label{width:auto;float:none}.job-manager-form .job-manager-term-checklist li li{margin:0 0 0 2em}div.job_listings{margin-bottom:1em}div.job_listings ul.job_listings{margin:0}.rp4wp-related-job_listing>ul,ul.job_listings{padding:0;margin:0;border-top:1px solid #eee}.rp4wp-related-job_listing>ul.loading,ul.job_listings.loading{min-height:96px;border-bottom:1px solid #eee;background:url(../images/ajax-loader.gif) no-repeat center 32px}.rp4wp-related-job_listing>ul li.job_listing,.rp4wp-related-job_listing>ul li.no_job_listings_found,ul.job_listings li.job_listing,ul.job_listings li.no_job_listings_found{list-style:none outside;padding:0;margin:0;border-bottom:1px solid #eee}.rp4wp-related-job_listing>ul li.job_listing.job_position_filled a,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_filled a,ul.job_listings li.job_listing.job_position_filled a,ul.job_listings li.no_job_listings_found.job_position_filled a{opacity:.25}.rp4wp-related-job_listing>ul li.job_listing.no_job_listings_found,.rp4wp-related-job_listing>ul li.no_job_listings_found.no_job_listings_found,ul.job_listings li.job_listing.no_job_listings_found,ul.job_listings li.no_job_listings_found.no_job_listings_found{padding:1em;border-bottom:1px solid #eee}.rp4wp-related-job_listing>ul li.job_listing a,.rp4wp-related-job_listing>ul li.no_job_listings_found a,ul.job_listings li.job_listing a,ul.job_listings li.no_job_listings_found a{display:block;padding:1em 1em 1em 2em;border:0;overflow:hidden;zoom:1;position:relative;line-height:1.5em;text-decoration:none}.rp4wp-related-job_listing>ul li.job_listing a:focus,.rp4wp-related-job_listing>ul li.job_listing a:hover,.rp4wp-related-job_listing>ul li.no_job_listings_found a:focus,.rp4wp-related-job_listing>ul li.no_job_listings_found a:hover,ul.job_listings li.job_listing a:focus,ul.job_listings li.job_listing a:hover,ul.job_listings li.no_job_listings_found a:focus,ul.job_listings li.no_job_listings_found a:hover{background-color:#fcfcfc}.rp4wp-related-job_listing>ul li.job_listing a img.company_logo,.rp4wp-related-job_listing>ul li.no_job_listings_found a img.company_logo,ul.job_listings li.job_listing a img.company_logo,ul.job_listings li.no_job_listings_found a img.company_logo{width:42px;height:42px;position:absolute;left:1em;float:left;margin-right:1em;vertical-align:middle;box-shadow:none}.rp4wp-related-job_listing>ul li.job_listing a div.location,.rp4wp-related-job_listing>ul li.job_listing a div.position,.rp4wp-related-job_listing>ul li.job_listing a ul.meta,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.location,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position,.rp4wp-related-job_listing>ul li.no_job_listings_found a ul.meta,ul.job_listings li.job_listing a div.location,ul.job_listings li.job_listing a div.position,ul.job_listings li.job_listing a ul.meta,ul.job_listings li.no_job_listings_found a div.location,ul.job_listings li.no_job_listings_found a div.position,ul.job_listings li.no_job_listings_found a ul.meta{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.rp4wp-related-job_listing>ul li.job_listing a div.position,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position,ul.job_listings li.job_listing a div.position,ul.job_listings li.no_job_listings_found a div.position{float:left;width:55%;padding:0 0 0 42px;line-height:1.5em}.rp4wp-related-job_listing>ul li.job_listing a div.position h3,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position h3,ul.job_listings li.job_listing a div.position h3,ul.job_listings li.no_job_listings_found a div.position h3{margin:0;padding:0;line-height:inherit;font-size:inherit}.rp4wp-related-job_listing>ul li.job_listing a div.position .company,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position .company,ul.job_listings li.job_listing a div.position .company,ul.job_listings li.no_job_listings_found a div.position .company{color:#999}.rp4wp-related-job_listing>ul li.job_listing a div.position .company .tagline,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position .company .tagline,ul.job_listings li.job_listing a div.position .company .tagline,ul.job_listings li.no_job_listings_found a div.position .company .tagline{margin-left:.5em}.rp4wp-related-job_listing>ul li.job_listing a div.location,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.location,ul.job_listings li.job_listing a div.location,ul.job_listings li.no_job_listings_found a div.location{float:left;text-align:left;width:25%;padding:0 0 0 1em;color:#999;line-height:1.5em}.rp4wp-related-job_listing>ul li.job_listing a .meta,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta,ul.job_listings li.job_listing a .meta,ul.job_listings li.no_job_listings_found a .meta{float:right;text-align:right;width:20%;padding:0 0 0 1em;margin:0;line-height:1.5em;color:#999;list-style:none outside}.rp4wp-related-job_listing>ul li.job_listing a .meta li,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta li,ul.job_listings li.job_listing a .meta li,ul.job_listings li.no_job_listings_found a .meta li{list-style:none outside;display:block;margin:0}.rp4wp-related-job_listing>ul li.job_listing a .meta .job-type,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta .job-type,ul.job_listings li.job_listing a .meta .job-type,ul.job_listings li.no_job_listings_found a .meta .job-type{font-weight:700}.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a,ul.job_listings li.job_listing.job_position_featured a,ul.job_listings li.no_job_listings_found.job_position_featured a{background:#fefee5}.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a:focus,.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a:hover,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a:focus,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a:hover,ul.job_listings li.job_listing.job_position_featured a:focus,ul.job_listings li.job_listing.job_position_featured a:hover,ul.job_listings li.no_job_listings_found.job_position_featured a:focus,ul.job_listings li.no_job_listings_found.job_position_featured a:hover{background-color:#fefed8}.widget ul.job_listings li.job_listing a{padding:1em 0}.widget ul.job_listings li.job_listing .position{float:none;width:auto;padding:0}.widget ul.job_listings li.job_listing ul.meta{float:none;width:auto;padding:0;margin:0;text-align:left}.widget ul.job_listings li.job_listing ul.meta li{float:none;display:inline;padding:0;margin:0 .5em 0 0;font-weight:400}.widget ul.job_listings li.job_listing ul.meta li:after{padding:0 0 0 .5em;content:"\2023"}.widget ul.job_listings li.job_listing ul.meta li:last-child:after{content:''}.job-manager .job-type,.job-types .job-type,.job_listing .job-type{color:#f08d3c}.job-manager .full-time,.job-types .full-time,.job_listing .full-time{color:#90da36}.job-manager .part-time,.job-types .part-time,.job_listing .part-time{color:#f08d3c}.job-manager .temporary,.job-types .temporary,.job_listing .temporary{color:#d93674}.job-manager .freelance,.job-types .freelance,.job_listing .freelance{color:#39c}.job-manager .internship,.job-types .internship,.job_listing .internship{color:#6033cc}.single_job_listing .company{position:relative;border:1px solid #eee;padding:1em;margin:0 0 2em;display:block;clear:both;min-height:3em;box-shadow:0 1px 1px rgba(0,0,0,.1)}.single_job_listing .company img{width:3em;height:3em;position:absolute;left:1em;float:left;vertical-align:middle;box-shadow:none}.single_job_listing .company .name{margin:0 0 0 3em;padding:0 0 0 1em;line-height:1.5em}.single_job_listing .company .name a{float:right;margin-left:1em}.single_job_listing .company .tagline{display:block;margin:0 0 0 42px;padding:0 0 0 1em;line-height:1.5em;font-style:italic;color:#999}.single_job_listing .company .website:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e809'}.single_job_listing .company .company_twitter:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e80a'}.single_job_listing .company .company_video{border-top:1px solid #eee;padding:1em 0 0;margin:1em 0 0;position:relative;padding-bottom:56.25%;padding-top:30px;height:0;overflow:hidden}.single_job_listing .company .company_video embed,.single_job_listing .company .company_video iframe,.single_job_listing .company .company_video object{position:absolute;top:0;left:0;width:100%;height:100%;margin:0;display:block}.single_job_listing .meta{list-style:none outside;padding:0;margin:0 0 1.5em;overflow:hidden;zoom:1;clear:both}.single_job_listing .meta li{margin:0 1em 0 0;padding:.5em;float:left;line-height:1em;color:#999}.single_job_listing .meta .job-type{color:#fff;background-color:#f08d3c}.single_job_listing .meta .full-time{background-color:#90da36}.single_job_listing .meta .part-time{background-color:#f08d3c}.single_job_listing .meta .temporary{background-color:#d93674}.single_job_listing .meta .freelance{background-color:#39c}.single_job_listing .meta .internship{background-color:#6033cc}.single_job_listing .meta .listing-expired,.single_job_listing .meta .position-filled{color:#b81c23}.single_job_listing .meta .location:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e81d'}.single_job_listing .meta .date-posted:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e80f'}.single_job_listing .meta .listing-expired:before,.single_job_listing .meta .position-filled:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e80e'}.single_job_listing .job_description{margin:0 0 1.5em}.job-manager-application-wrapper{clear:both;border:1px solid #eee;padding:.75em 1em 0;margin:1em 0;line-height:1.5em;display:block;position:relative;box-shadow:0 1px 1px rgba(0,0,0,.1)}.job-manager-application-wrapper .application,.single_job_listing .application{padding:0;margin:0 0 1em;overflow:hidden}.job-manager-application-wrapper .application .application_button,.job-manager-application-wrapper .application .application_details,.single_job_listing .application .application_button,.single_job_listing .application .application_details{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job-manager-application-wrapper .application .application_button,.single_job_listing .application .application_button{text-align:center;font-size:1.1em;line-height:1em;display:inline-block;margin:0 .5em 0 0;padding:.90909091em 2em;outline:0}.job-manager-application-wrapper .application .application_details,.single_job_listing .application .application_details{clear:both;border:1px solid #eee;padding:.75em 1em 0;margin:1em 0;line-height:1.5em;display:block;position:relative;box-shadow:0 1px 1px rgba(0,0,0,.1)}.job-manager-application-wrapper .application .application_details p,.single_job_listing .application .application_details p{margin:0 0 .75em}.job-manager-application-wrapper .application .application_details:before,.single_job_listing .application .application_details:before{content:"";position:absolute;margin:-10px 0 0 0;top:0;left:5em;width:0;height:0;border-left:10px solid transparent;border-right:10px solid transparent;border-bottom:10px solid #eee}.job-manager-application-wrapper .application .application_details:after,.single_job_listing .application .application_details:after{content:"";position:absolute;margin:-9px 0 0 1px;left:5em;top:0;width:0;height:0;border-left:9px solid transparent;border-right:9px solid transparent;border-bottom:9px solid #fff}.job_filters{background:#eee;zoom:1}.job_filters:after,.job_filters:before{content:"";display:table}.job_filters:after{clear:both}.job_filters .search_jobs{padding:1em;zoom:1}.job_filters .search_jobs:after,.job_filters .search_jobs:before{content:"";display:table}.job_filters .search_jobs:after{clear:both}.job_filters .search_jobs div{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job_filters .search_jobs div label{display:none}.job_filters .search_jobs div.filter_first,.job_filters .search_jobs div.search_keywords{float:left;padding-right:.5em;width:50%}.job_filters .search_jobs div.filter_last,.job_filters .search_jobs div.search_location{float:right;padding-left:.5em;width:50%}.job_filters .search_jobs div.filter_wide,.job_filters .search_jobs div.search_categories{padding-top:.5em;clear:both;width:100%}.job_filters .search_jobs div .showing_jobs a{padding:.25em}.job_filters .search_jobs div .showing_jobs a.active{background:#ddd;text-decoration:none}.job_filters .search_jobs input,.job_filters .search_jobs select{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%}.job_filters .job_types{list-style:none outside;margin:0;padding:0;font-size:.83em;background:#f9f9f9;border-top:1px solid #e5e5e5;zoom:1}.job_filters .job_types:after,.job_filters .job_types:before{content:"";display:table}.job_filters .job_types:after{clear:both}.job_filters .job_types li{margin:0;padding:.5em 1em;float:left;border-right:1px solid #eee}.job_filters .job_types li:last-child{border-right:0}.job_filters .showing_jobs{padding:.5em 1em;display:none;font-size:.83em;background:#f9f9f9;border-top:1px solid #e5e5e5}.job_filters .showing_jobs a{float:right;padding-left:10px;border:0}div.job_listings .job-manager-pagination{text-align:center;display:block;padding:1em 0;border-bottom:1px solid #eee;line-height:1}div.job_listings .load_previous{border-top:1px solid #eee}div.job_listings .load_more_jobs+ul.job_listings{border-top:0}div.job_listings .load_more_jobs{text-align:center;display:block;padding:1em 1em 1em 2em;border-bottom:1px solid #eee;font-weight:700}div.job_listings .load_more_jobs.loading{background:url(../images/ajax-loader.gif) no-repeat center}div.job_listings .load_more_jobs.loading strong{visibility:hidden}div.job_listings .load_more_jobs:focus,div.job_listings .load_more_jobs:hover{background-color:#fcfcfc;border-bottom:1px solid #eee}.job_listing_preview{padding:0 1em 1em;border:5px solid #eee}.entry-header .attachment-post-thumbnail{display:none}.entry-content .job_listing_preview_title,.job_listing_preview_title{padding:.5em 1em;vertical-align:middle;position:relative;background:#eee}.entry-content .job_listing_preview_title h2,.job_listing_preview_title h2{margin:0;clear:none}.entry-content .job_listing_preview_title .button,.job_listing_preview_title .button{float:right;margin-left:.25em}.job_summary_shortcode{border:1px solid #ccc;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;text-align:center;position:relative;box-shadow:0 2px 4px rgba(0,0,0,.1),inset 0 1px 0 rgba(255,255,255,.4);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.job_summary_shortcode.aligncenter{display:block;margin:2em auto}.job_summary_shortcode.alignleft{float:left;margin:0 2em 2em 0}.job_summary_shortcode.alignright{float:right;margin:0 0 2em 2em}.job_summary_shortcode a{text-decoration:none;color:inherit}.job_summary_shortcode img{margin:0;padding:0;display:block;width:100%;-moz-border-radius:0;-webkit-border-radius:0;-moz-border-top-left-radius:3px;-moz-border-top-right-radius:3px;-webkit-border-top-left-radius:3px;-webkit-border-top-right-radius:3px;border-radius:0;border-top-left-radius:3px;border-top-right-radius:3px;box-shadow:inset 0 1px 0 rgba(255,255,255,.4)}.job_summary_shortcode .job_summary_content{padding:0 1em}.job_summary_shortcode .meta{font-style:italic;color:#777}.job_summary_shortcode .job-type{-moz-border-radius:1em;-webkit-border-radius:1em;border-radius:1em;color:#fff;text-shadow:0 1px 0 rgba(255,255,255,.5);box-shadow:0 2px 4px rgba(0,0,0,.1),inset 0 1px 0 rgba(255,255,255,.4);position:absolute;top:0;right:0;padding:.5em;height:1em;width:auto;min-width:1em;font-size:1em;text-align:center;vertical-align:middle;line-height:1em;margin:-.5em -.5em 0 0}.job_summary_shortcode .job-type.full-time{background-color:#90da36}.job_summary_shortcode .job-type.part-time{background-color:#f08d3c}.job_summary_shortcode .job-type.temporary{background-color:#d93674}.job_summary_shortcode .job-type.freelance{background-color:#39c}.job_summary_shortcode .job-type.internship{background-color:#6033cc}#job-manager-job-dashboard .account-sign-in .button{margin-right:.5em}#job-manager-job-dashboard .account-sign-in .button:before{display:inline-block;width:16px;height:16px;-webkit-font-smoothing:antialiased;font-family:job-manager!important;text-decoration:none;font-weight:400;font-style:normal;vertical-align:top;font-size:16px;margin:0 2px 0 0;*overflow:auto;*zoom:1;*display:inline;content:'\e808'}#job-manager-job-dashboard table ul.job-dashboard-actions{margin:0;padding:0;visibility:hidden;font-size:.92em}#job-manager-job-dashboard table ul.job-dashboard-actions li{float:none;display:inline;padding:0;margin:0 .5em 0 0;font-weight:400;list-style:none outside}#job-manager-job-dashboard table ul.job-dashboard-actions li:after{padding:0 0 0 .5em;content:"\2023"}#job-manager-job-dashboard table ul.job-dashboard-actions li:last-child:after{content:''}#job-manager-job-dashboard table ul.job-dashboard-actions li .job-dashboard-action-delete{color:red}#job-manager-job-dashboard table tr:focus ul.job-dashboard-actions,#job-manager-job-dashboard table tr:hover ul.job-dashboard-actions{visibility:visible}#job-manager-job-dashboard table td,#job-manager-job-dashboard table th{padding:.5em 1em .5em 0}#job-manager-job-dashboard table .job_title small{color:#999}#content nav.job-manager-pagination,#job-manager-job-dashboard table .applications,#job-manager-job-dashboard table .expires,#job-manager-job-dashboard table .filled,nav.job-manager-pagination{text-align:center}#content nav.job-manager-pagination ul,nav.job-manager-pagination ul{display:inline-block;white-space:nowrap;padding:0;clear:both;border-left:1px solid #eee;margin:1px}#content nav.job-manager-pagination ul li,nav.job-manager-pagination ul li{border-right:1px solid #eee;border-top:1px solid #eee;border-bottom:1px solid #eee;padding:0;margin:0;float:left;display:inline;overflow:hidden}#content nav.job-manager-pagination ul li a,#content nav.job-manager-pagination ul li span,nav.job-manager-pagination ul li a,nav.job-manager-pagination ul li span{margin:0;text-decoration:none;line-height:1em;font-size:1em;font-weight:400;padding:.5em;min-width:1em;display:block;border:0}#content nav.job-manager-pagination ul li a:focus,#content nav.job-manager-pagination ul li a:hover,#content nav.job-manager-pagination ul li span.current,nav.job-manager-pagination ul li a:focus,nav.job-manager-pagination ul li a:hover,nav.job-manager-pagination ul li span.current{background:#eee;color:#888}@media only screen and (max-width:767px){ul.job_listings li.job_listing a,ul.job_listings li.no_job_listings_found a{padding:1em}ul.job_listings li.job_listing a img.company_logo,ul.job_listings li.no_job_listings_found a img.company_logo{visibility:hidden}ul.job_listings li.job_listing a div.position,ul.job_listings li.no_job_listings_found a div.position{float:left;width:60%;padding:0}ul.job_listings li.job_listing a div.location,ul.job_listings li.no_job_listings_found a div.location{float:right;width:40%;line-height:2em;font-size:.75em;padding:0 0 0 1em;text-align:right}ul.job_listings li.job_listing a .meta,ul.job_listings li.no_job_listings_found a .meta{float:right;width:40%;line-height:2em;font-size:.75em}ul.job_listings li.job_listing a .meta li,ul.job_listings li.no_job_listings_found a .meta li{font-size:1em}}.chosen-container{width:100%!important}.twenty-eleven ul.job_listings li.job_listing,.twenty-eleven ul.job_listings li.no_job_listings_found{padding:0!important}.twenty-ten .chosen-choices,.twenty-ten .job_types{margin:0!important}.rtl .job-manager-form label{float:right}.rtl .entry-content .job_listing_preview_title .button,.rtl .job-manager-form div.field,.rtl .job_listing_preview_title .button{float:left}.rtl .single_job_listing .meta li{float:right;margin:0 0 0 1em}
assets/css/frontend.less ADDED
@@ -0,0 +1,957 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "mixins";
2
+ @import "icons";
3
+
4
+ @primary: #999999; /* Primary colour for buttons (alt) */
5
+ @primarytext: desaturate(lighten(@primary,50%),18%); /* Text on primary colour bg */
6
+
7
+ @secondary: desaturate(lighten(@primary,40%),18%); /* Secondary buttons */
8
+ @secondarytext: desaturate(darken(@secondary,60%),18%); /* Text on secondary colour bg */
9
+
10
+ @highlight: spin( @primary, 150 ); /* Prices, In stock labels, sales flash */
11
+ @highlightext: desaturate(lighten(@highlight,50%),18%); /* Text on highlight colour bg */
12
+
13
+ @contentbg: #fff; /* Content BG - Tabs (active state) */
14
+ @subtext: #777; /* small, breadcrumbs etc */
15
+
16
+ .display-icon {
17
+ display: inline-block;
18
+ width: 16px;
19
+ height: 16px;
20
+ -webkit-font-smoothing: antialiased;
21
+ font-size: 16px;
22
+ font-family: "job-manager" !important;
23
+ text-decoration: none;
24
+ font-weight: normal;
25
+ font-style: normal;
26
+ vertical-align: top;
27
+ font-size: 16px;
28
+ margin: 0 2px 0 0;
29
+
30
+ *overflow: auto;
31
+ *zoom: 1;
32
+ *display: inline;
33
+ }
34
+
35
+ /* =Global styles/layout
36
+ -------------------------------------------------------------- */
37
+ .job-manager-message, .job-manager-error, .job-manager-info {
38
+ padding: 1em 2em 1em 3.5em !important;
39
+ margin: 0 0 2em !important;
40
+ position: relative;
41
+ background-color: lighten(@secondary,5%);
42
+ color: @secondarytext;
43
+ border-top: 3px solid @primary;
44
+ list-style: none outside !important;
45
+ width: auto;
46
+ .clearfix;
47
+ box-shadow: 0 1px 1px rgba(0,0,0,0.2);
48
+ &:before {
49
+ content: "";
50
+ font-family: sans-serif;
51
+ display: inline-block;
52
+ position: absolute;
53
+ top: 1em;
54
+ left: 1.5em;
55
+ }
56
+ li {
57
+ list-style: none outside !important;
58
+ padding-left: 0 !important;
59
+ margin-left: 0 !important;
60
+ }
61
+ &.job-manager-message {
62
+ border-top-color: #8fae1b;
63
+ &:before {
64
+ color:#8fae1b;
65
+ content: "\2713";
66
+ }
67
+ }
68
+ &.job-manager-info {
69
+ border-top-color: #1e85be;
70
+ &:before {
71
+ color:#1e85be;
72
+ content: "i";
73
+ font-family: Times, Georgia, serif;
74
+ font-style: italic;
75
+ }
76
+ }
77
+ &.job-manager-error {
78
+ border-top-color: #b81c23;
79
+ &:before {
80
+ color:#b81c23;
81
+ content: "\00d7";
82
+ font-weight: 700;
83
+ }
84
+ }
85
+ }
86
+
87
+ .job-manager-form {
88
+ fieldset {
89
+ margin: 0 0 1em 0;
90
+ padding: 0 0 1em 0;
91
+ line-height: 2em;
92
+ border: 0;
93
+ border-bottom: 1px solid #eee;
94
+ .clearfix;
95
+ label {
96
+ display: block;
97
+ margin: 0;
98
+ width: 29%;
99
+ float: left;
100
+ vertical-align: middle;
101
+ small {
102
+ opacity: .75;
103
+ font-size: 0.83em;
104
+ }
105
+ }
106
+ div.field {
107
+ width: 70%;
108
+ float: right;
109
+ vertical-align: middle;
110
+ }
111
+ .wp-editor-container {
112
+ border: 1px solid #ccc;
113
+ -webkit-border-radius: 3px;
114
+ -moz-border-radius: 3px;
115
+ border-radius: 3px;
116
+ }
117
+ .account-sign-in {
118
+ .button {
119
+ margin-right: .5em;
120
+ &:before {
121
+ .display-icon;
122
+ content: '\e808';
123
+ }
124
+ }
125
+ }
126
+ abbr.required {
127
+ color: red;
128
+ font-weight: bold;
129
+ border: 0;
130
+ }
131
+ input.input-text, textarea, select {
132
+ margin: 0;
133
+ vertical-align: middle;
134
+ width: 100%;
135
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
136
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
137
+ box-sizing: border-box; /* Opera/IE 8+ */
138
+ }
139
+ small.description {
140
+ opacity: .75;
141
+ font-size: 0.83em;
142
+ margin: 1.2em 0 0 0;
143
+ display: block;
144
+ line-height: 1.2em;
145
+ }
146
+ .job-manager-uploaded-files {
147
+ display: table;
148
+ .job-manager-uploaded-file {
149
+ line-height: 2em;
150
+ font-style: italic;
151
+ margin-bottom: 1em;
152
+ display: block;
153
+ .job-manager-uploaded-file-preview {
154
+ img {
155
+ height: 64px;
156
+ margin: 0;
157
+ vertical-align: top;
158
+ }
159
+ a {
160
+ line-height: 64px;
161
+ display: inline-block;
162
+ padding: 0 0 0 1em;
163
+ }
164
+ }
165
+ .job-manager-uploaded-file-name {
166
+ display: block;
167
+ }
168
+ }
169
+ }
170
+ }
171
+ .submit-job {
172
+ padding: 1em 0;
173
+ }
174
+ .job-manager-term-checklist {
175
+ list-style: none outside;
176
+ max-height: 200px;
177
+ overflow: auto;
178
+ margin: 0;
179
+ li {
180
+ list-style: none outside;
181
+ margin: 0;
182
+ display: block;
183
+ float: none;
184
+ label {
185
+ width: auto;
186
+ float: none;
187
+ }
188
+ li {
189
+ margin: 0 0 0 2em;
190
+ }
191
+ }
192
+ }
193
+ }
194
+ div.job_listings {
195
+ margin-bottom: 1em;
196
+ ul.job_listings {
197
+ margin: 0;
198
+ }
199
+ }
200
+ .rp4wp-related-job_listing > ul,
201
+ ul.job_listings {
202
+ padding: 0;
203
+ margin: 0;
204
+ border-top: 1px solid #eee;
205
+
206
+ &.loading {
207
+ min-height: 96px;
208
+ border-bottom: 1px solid #eee;
209
+ background: url(../images/ajax-loader.gif) no-repeat center 32px;
210
+ }
211
+ li.job_listing, li.no_job_listings_found {
212
+ list-style: none outside;
213
+ padding: 0;
214
+ margin: 0;
215
+ border-bottom: 1px solid #eee;
216
+
217
+ &.job_position_filled {
218
+ a {
219
+ opacity: 0.25;
220
+ }
221
+ }
222
+ &.no_job_listings_found {
223
+ padding: 1em;
224
+ border-bottom: 1px solid #eee;
225
+ }
226
+ a {
227
+ display: block;
228
+ padding: 1em 1em 1em 2em;
229
+ border: 0;
230
+ overflow: hidden;
231
+ zoom: 1;
232
+ position: relative;
233
+ line-height: 1.5em;
234
+ text-decoration: none;
235
+
236
+ &:hover, &:focus {
237
+ background-color: #fcfcfc;
238
+ }
239
+ img.company_logo {
240
+ width: 42px;
241
+ height: 42px;
242
+ position: absolute;
243
+ left: 1em;
244
+ float: left;
245
+ margin-right: 1em;
246
+ vertical-align: middle;
247
+ box-shadow: none;
248
+ }
249
+ div.position, div.location, ul.meta {
250
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
251
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
252
+ box-sizing: border-box; /* Opera/IE 8+ */
253
+ }
254
+ div.position {
255
+ float: left;
256
+ width: 55%;
257
+ padding: 0 0 0 42px;
258
+ line-height: 1.5em;
259
+ h3 {
260
+ margin: 0;
261
+ padding: 0;
262
+ line-height: inherit;
263
+ font-size: inherit;
264
+ }
265
+ .company {
266
+ color: #999;
267
+ .tagline {
268
+ margin-left: .5em;
269
+ }
270
+ }
271
+ }
272
+ div.location {
273
+ float: left;
274
+ text-align: left;
275
+ width: 25%;
276
+ padding: 0 0 0 1em;
277
+ color: #999;
278
+ line-height: 1.5em;
279
+ }
280
+ .meta {
281
+ float: right;
282
+ text-align: right;
283
+ width: 20%;
284
+ padding: 0 0 0 1em;
285
+ margin: 0;
286
+ line-height: 1.5em;
287
+ color: #999;
288
+ list-style: none outside;
289
+ li {
290
+ list-style: none outside;
291
+ display: block;
292
+ margin: 0;
293
+ }
294
+ .job-type {
295
+ font-weight: bold;
296
+ }
297
+ }
298
+ }
299
+ &.job_position_featured {
300
+ a {
301
+ background: #fefee5;
302
+
303
+ &:hover, &:focus {
304
+ background-color: #fefed8;
305
+ }
306
+ }
307
+ }
308
+ }
309
+ }
310
+ .widget {
311
+ ul.job_listings {
312
+ li.job_listing {
313
+ a {
314
+ padding: 1em 0;
315
+ }
316
+ .position {
317
+ float: none;
318
+ width: auto;
319
+ padding: 0;
320
+ }
321
+ ul.meta {
322
+ float: none;
323
+ width: auto;
324
+ padding: 0;
325
+ margin: 0;
326
+ text-align: left;
327
+ li {
328
+ float: none;
329
+ display: inline;
330
+ padding: 0;
331
+ margin: 0 .5em 0 0;
332
+ font-weight: normal;
333
+
334
+ &:after {
335
+ padding: 0 0 0 .5em;
336
+ content: "\2023";
337
+ }
338
+ &:last-child:after {
339
+ content: '';
340
+ }
341
+ }
342
+ }
343
+ }
344
+ }
345
+ }
346
+ .job-manager, .job_listing, .job-types {
347
+ .job-type {
348
+ color: @part-time;
349
+ }
350
+ .full-time {
351
+ color: @full-time;
352
+ }
353
+ .part-time {
354
+ color: @part-time;
355
+ }
356
+ .temporary {
357
+ color: @temporary;
358
+ }
359
+ .freelance {
360
+ color: @freelance;
361
+ }
362
+ .internship {
363
+ color: @internship;
364
+ }
365
+ }
366
+ .single_job_listing {
367
+ .company {
368
+ position: relative;
369
+ border: 1px solid #eee;
370
+ padding: 1em;
371
+ margin: 0 0 2em;
372
+ display: block;
373
+ clear: both;
374
+ min-height: 3em;
375
+ box-shadow: 0 1px 1px rgba(0,0,0,0.1);
376
+
377
+ img {
378
+ width: 3em;
379
+ height: 3em;
380
+ position: absolute;
381
+ left: 1em;
382
+ float: left;
383
+ vertical-align: middle;
384
+ box-shadow: none;
385
+ }
386
+ .name {
387
+ margin: 0 0 0 3em;
388
+ padding: 0 0 0 1em;
389
+ line-height: 1.5em;
390
+ a {
391
+ float: right;
392
+ margin-left: 1em;
393
+ }
394
+ }
395
+ .tagline {
396
+ display: block;
397
+ margin: 0 0 0 42px;
398
+ padding: 0 0 0 1em;
399
+ line-height: 1.5em;
400
+ font-style: italic;
401
+ color: #999;
402
+ }
403
+ .website:before {
404
+ .display-icon;
405
+ content: '\e809';
406
+ }
407
+ .company_twitter:before {
408
+ .display-icon;
409
+ content: '\e80a';
410
+ }
411
+ .company_video {
412
+ border-top: 1px solid #eee;
413
+ padding: 1em 0 0;
414
+ margin: 1em 0 0 0;
415
+ position: relative;
416
+ padding-bottom: 56.25%;
417
+ padding-top: 30px;
418
+ height: 0;
419
+ overflow: hidden;
420
+ iframe,
421
+ object,
422
+ embed {
423
+ position: absolute;
424
+ top: 0;
425
+ left: 0;
426
+ width: 100%;
427
+ height: 100%;
428
+ margin: 0;
429
+ display: block;
430
+ }
431
+ }
432
+ }
433
+ .meta {
434
+ list-style: none outside;
435
+ padding: 0;
436
+ margin: 0 0 1.5em;
437
+ overflow: hidden;
438
+ zoom: 1;
439
+ clear: both;
440
+ li {
441
+ margin: 0 1em 0 0;
442
+ padding: .5em;
443
+ float: left;
444
+ line-height: 1em;
445
+ color: #999;
446
+ }
447
+ .job-type {
448
+ color: #fff;
449
+ background-color: @part-time;
450
+ }
451
+ .full-time {
452
+ background-color: @full-time;
453
+ }
454
+ .part-time {
455
+ background-color: @part-time;
456
+ }
457
+ .temporary {
458
+ background-color: @temporary;
459
+ }
460
+ .freelance {
461
+ background-color: @freelance;
462
+ }
463
+ .internship {
464
+ background-color: @internship;
465
+ }
466
+ .position-filled, .listing-expired {
467
+ color: #b81c23;
468
+ }
469
+ .location:before {
470
+ .display-icon;
471
+ content: '\e81d';
472
+ }
473
+ .date-posted:before {
474
+ .display-icon;
475
+ content: '\e80f';
476
+ }
477
+ .position-filled:before, .listing-expired:before {
478
+ .display-icon;
479
+ content: '\e80e';
480
+ }
481
+ }
482
+ .job_description {
483
+ margin: 0 0 1.5em;
484
+ }
485
+ }
486
+ .job-manager-application-wrapper {
487
+ clear: both;
488
+ border: 1px solid #eee;
489
+ padding: .75em 1em 0;
490
+ margin: 1em 0;
491
+ line-height: 1.5em;
492
+ display: block;
493
+ position: relative;
494
+ box-shadow: 0 1px 1px rgba(0,0,0,0.1);
495
+ }
496
+ .single_job_listing, .job-manager-application-wrapper {
497
+ .application {
498
+ padding: 0;
499
+ margin: 0 0 1em;
500
+ overflow: hidden;
501
+
502
+ .application_button, .application_details {
503
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
504
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
505
+ box-sizing: border-box; /* Opera/IE 8+ */
506
+ }
507
+ .application_button {
508
+ text-align: center;
509
+ font-size: 1.1em;
510
+ line-height: 1em;
511
+ display: inline-block;
512
+ margin: 0 .5em 0 0;
513
+ padding: 1/1.1em 2em;
514
+ outline: 0;
515
+ }
516
+ .application_details {
517
+ clear: both;
518
+ border: 1px solid #eee;
519
+ padding: .75em 1em 0;
520
+ margin: 1em 0;
521
+ line-height: 1.5em;
522
+ display: block;
523
+ position: relative;
524
+ box-shadow: 0 1px 1px rgba(0,0,0,0.1);
525
+
526
+ p {
527
+ margin: 0 0 .75em;
528
+ }
529
+
530
+ &:before {
531
+ content: "";
532
+ position: absolute;
533
+ margin: -10px 0 0 0;
534
+ top: 0;
535
+ left: 5em;
536
+ width: 0;
537
+ height: 0;
538
+ border-left: 10px solid transparent;
539
+ border-right: 10px solid transparent;
540
+ border-bottom:10px solid #eee;
541
+ }
542
+ &:after {
543
+ content: "";
544
+ position: absolute;
545
+ margin: -9px 0 0 1px;
546
+ left: 5em;
547
+ top: 0;
548
+ width: 0;
549
+ height: 0;
550
+ border-left: 9px solid transparent;
551
+ border-right: 9px solid transparent;
552
+ border-bottom: 9px solid #fff;
553
+ }
554
+ }
555
+ }
556
+ }
557
+ .job_filters {
558
+ background: #eee;
559
+ .clearfix;
560
+
561
+ .search_jobs {
562
+ padding: 1em;
563
+ .clearfix;
564
+ div {
565
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
566
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
567
+ box-sizing: border-box; /* Opera/IE 8+ */
568
+
569
+ label {
570
+ display: none;
571
+ }
572
+ &.search_keywords, &.filter_first {
573
+ float: left;
574
+ padding-right: .5em;
575
+ width: 50%;
576
+ }
577
+ &.search_location, &.filter_last {
578
+ float: right;
579
+ padding-left: .5em;
580
+ width: 50%;
581
+ }
582
+ &.search_categories, &.filter_wide {
583
+ padding-top: .5em;
584
+ clear: both;
585
+ width: 100%;
586
+ }
587
+ .showing_jobs {
588
+ a {
589
+ padding: .25em;
590
+ }
591
+ a.active {
592
+ background: #ddd;
593
+ text-decoration: none;
594
+ }
595
+ }
596
+ }
597
+ input, select {
598
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
599
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
600
+ box-sizing: border-box; /* Opera/IE 8+ */
601
+ width: 100%;
602
+ }
603
+ }
604
+ .job_types {
605
+ list-style: none outside;
606
+ margin: 0;
607
+ padding: 0;
608
+ font-size: 0.83em;
609
+ background: #f9f9f9;
610
+ border-top: 1px solid #e5e5e5;
611
+ .clearfix;
612
+ li {
613
+ margin: 0;
614
+ padding: .5em 1em;
615
+ float: left;
616
+ border-right: 1px solid #eee;
617
+
618
+ &:last-child {
619
+ border-right: 0;
620
+ }
621
+ }
622
+ }
623
+
624
+ .showing_jobs {
625
+ padding: .5em 1em;
626
+ display: none;
627
+ font-size: 0.83em;
628
+ background: #f9f9f9;
629
+ border-top: 1px solid #e5e5e5;
630
+
631
+ a {
632
+ float: right;
633
+ padding-left: 10px;
634
+ border: 0;
635
+ }
636
+ }
637
+ }
638
+ div.job_listings {
639
+ .job-manager-pagination {
640
+ text-align: center;
641
+ display: block;
642
+ padding: 1em 0 1em 0;
643
+ border-bottom: 1px solid #eee;
644
+ line-height: 1;
645
+ }
646
+ .load_previous {
647
+ border-top: 1px solid #eee;
648
+ }
649
+ .load_more_jobs + ul.job_listings {
650
+ border-top: 0;
651
+ }
652
+ .load_more_jobs {
653
+ text-align: center;
654
+ display: block;
655
+ padding: 1em 1em 1em 2em;
656
+ border-bottom: 1px solid #eee;
657
+ font-weight: bold;
658
+ &.loading {
659
+ background: url(../images/ajax-loader.gif) no-repeat center;
660
+ strong {
661
+ visibility: hidden;
662
+ }
663
+ }
664
+ &:hover, &:focus {
665
+ background-color: #fcfcfc;
666
+ border-bottom: 1px solid #eee;
667
+ }
668
+ }
669
+ }
670
+ .job_listing_preview {
671
+ padding: 0 1em 1em;
672
+ border: 5px solid #eee;
673
+ }
674
+ .entry-header .attachment-post-thumbnail {
675
+ display: none;
676
+ }
677
+ .job_listing_preview_title, .entry-content .job_listing_preview_title {
678
+ padding: .5em 1em;
679
+ vertical-align: middle;
680
+ position: relative;
681
+ background: #eee;
682
+ h2 {
683
+ margin: 0;
684
+ clear: none;
685
+ }
686
+ .button {
687
+ float: right;
688
+ margin-left: .25em;
689
+ }
690
+ }
691
+ .job_summary_shortcode {
692
+ border: 1px solid #ccc;
693
+ -moz-border-radius: 4px;
694
+ -webkit-border-radius: 4px;
695
+ border-radius: 4px;
696
+ text-align: center;
697
+ position: relative;
698
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1),inset 0 1px 0 rgba(255,255,255,0.4);
699
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
700
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
701
+ box-sizing: border-box; /* Opera/IE 8+ */
702
+
703
+ &.aligncenter {
704
+ display: block;
705
+ margin: 2em auto 2em;
706
+ }
707
+
708
+ &.alignleft {
709
+ float: left;
710
+ margin: 0 2em 2em 0;
711
+ }
712
+
713
+ &.alignright {
714
+ float: right;
715
+ margin: 0 0 2em 2em;
716
+ }
717
+
718
+ a {
719
+ text-decoration: none;
720
+ color: inherit;
721
+ }
722
+
723
+ img {
724
+ margin: 0;
725
+ padding: 0;
726
+ display: block;
727
+ width: 100%;
728
+ -moz-border-radius: 0;
729
+ -webkit-border-radius: 0;
730
+ -moz-border-top-left-radius: 3px;
731
+ -moz-border-top-right-radius: 3px;
732
+ -webkit-border-top-left-radius: 3px;
733
+ -webkit-border-top-right-radius: 3px;
734
+ border-radius: 0;
735
+ border-top-left-radius: 3px;
736
+ border-top-right-radius: 3px;
737
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.4);
738
+ }
739
+
740
+ .job_summary_content {
741
+ padding: 0 1em;
742
+ }
743
+
744
+ .meta {
745
+ font-style: italic;
746
+ color: #777;
747
+ }
748
+
749
+ .job-type {
750
+ -moz-border-radius: 1em;
751
+ -webkit-border-radius: 1em;
752
+ border-radius: 1em;
753
+ color: #fff;
754
+ text-shadow: 0 1px 0 rgba(255,255,255,0.5);
755
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1), inset 0 1px 0 rgba(255,255,255,0.4);
756
+ position: absolute;
757
+ top: 0;
758
+ right: 0;
759
+ padding: .5em;
760
+ height: 1em;
761
+ width: auto;
762
+ min-width: 1em;
763
+ font-size: 1em;
764
+ text-align: center;
765
+ vertical-align: middle;
766
+ line-height: 1em;
767
+ margin: -.5em -.5em 0 0;
768
+
769
+ &.full-time {
770
+ background-color: @full-time;
771
+ }
772
+ &.part-time {
773
+ background-color: @part-time;
774
+ }
775
+ &.temporary {
776
+ background-color: @temporary;
777
+ }
778
+ &.freelance {
779
+ background-color: @freelance;
780
+ }
781
+ &.internship {
782
+ background-color: @internship;
783
+ }
784
+ }
785
+ }
786
+
787
+ #job-manager-job-dashboard {
788
+ .account-sign-in {
789
+ .button {
790
+ margin-right: .5em;
791
+ &:before {
792
+ .display-icon;
793
+ content: '\e808';
794
+ }
795
+ }
796
+ }
797
+ table {
798
+ ul.job-dashboard-actions {
799
+ margin: 0;
800
+ padding:0;
801
+ visibility: hidden;
802
+ font-size: 0.92em;
803
+ li {
804
+ float: none;
805
+ display: inline;
806
+ padding: 0;
807
+ margin: 0 .5em 0 0;
808
+ font-weight: normal;
809
+ list-style: none outside;
810
+
811
+ &:after {
812
+ padding: 0 0 0 .5em;
813
+ content: "\2023";
814
+ }
815
+ &:last-child:after {
816
+ content: '';
817
+ }
818
+ .job-dashboard-action-delete {
819
+ color: red;
820
+ }
821
+ }
822
+ }
823
+ tr:hover, tr:focus {
824
+ ul.job-dashboard-actions {
825
+ visibility: visible;
826
+ }
827
+ }
828
+ td, th {
829
+ padding: .5em 1em .5em 0;
830
+ }
831
+ .job_title small {
832
+ color: #999;
833
+ }
834
+ .filled, .expires, .applications {
835
+ text-align: center;
836
+ }
837
+ }
838
+ }
839
+
840
+ nav.job-manager-pagination, #content nav.job-manager-pagination {
841
+ text-align: center;
842
+ ul {
843
+ display: inline-block;
844
+ white-space: nowrap;
845
+ padding:0;
846
+ clear: both;
847
+ border-left: 1px solid #eee;
848
+ margin: 1px;
849
+ li {
850
+ border-right: 1px solid #eee;
851
+ border-top: 1px solid #eee;
852
+ border-bottom: 1px solid #eee;
853
+ padding: 0;
854
+ margin: 0;
855
+ float: left;
856
+ display: inline;
857
+ overflow: hidden;
858
+ a, span {
859
+ margin: 0;
860
+ text-decoration: none;
861
+ padding: 0;
862
+ line-height: 1em;
863
+ font-size: 1em;
864
+ font-weight: normal;
865
+ padding: .5em;
866
+ min-width: 1em;
867
+ display: block;
868
+ border: 0;
869
+ }
870
+ span.current, a:hover, a:focus {
871
+ background: #eee;
872
+ color: darken( #eee, 40 );
873
+ }
874
+ }
875
+ }
876
+ }
877
+
878
+ /**
879
+ * Mobile styles
880
+ */
881
+ @media only screen and (max-width: 767px) {
882
+ ul.job_listings {
883
+ li.job_listing, li.no_job_listings_found {
884
+ a {
885
+ padding: 1em;
886
+ img.company_logo {
887
+ visibility: hidden;
888
+ }
889
+ div.position {
890
+ float: left;
891
+ width: 60%;
892
+ padding: 0;
893
+ }
894
+ div.location {
895
+ float: right;
896
+ width: 40%;
897
+ line-height: 2em;
898
+ font-size: .75em;
899
+ padding: 0 0 0 1em;
900
+ text-align: right;
901
+ }
902
+ .meta {
903
+ float: right;
904
+ width: 40%;
905
+ line-height: 2em;
906
+ font-size: .75em;
907
+ li {
908
+ font-size: 1em;
909
+ }
910
+ }
911
+ }
912
+ }
913
+ }
914
+ }
915
+
916
+ // Chosen fixes
917
+ .chosen-container {
918
+ width: 100% !important;
919
+ }
920
+
921
+ // Default theme fixes
922
+ .twenty-eleven {
923
+ ul.job_listings {
924
+ li.job_listing, li.no_job_listings_found {
925
+ padding: 0 !important;
926
+ }
927
+ }
928
+ }
929
+ .twenty-ten {
930
+ .job_types, .chosen-choices {
931
+ margin: 0 !important;
932
+ }
933
+ }
934
+
935
+ .rtl {
936
+ .job-manager-form {
937
+ label {
938
+ float: right;
939
+ }
940
+ div.field {
941
+ float: left;
942
+ }
943
+ }
944
+ .job_listing_preview_title, .entry-content .job_listing_preview_title {
945
+ .button {
946
+ float: left;
947
+ }
948
+ }
949
+ .single_job_listing {
950
+ .meta {
951
+ li {
952
+ float: right;
953
+ margin: 0 0 0 1em;
954
+ }
955
+ }
956
+ }
957
+ }
assets/css/icons.less ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @font-face {
2
+ font-family: 'job-manager';
3
+ src: url('../font/job-manager.eot?4963673');
4
+ src: url('../font/job-manager.eot?4963673#iefix') format('embedded-opentype'),
5
+ url('../font/job-manager.woff?4963673') format('woff'),
6
+ url('../font/job-manager.ttf?4963673') format('truetype'),
7
+ url('../font/job-manager.svg?4963673#job-manager') format('svg');
8
+ font-weight: normal;
9
+ font-style: normal;
10
+ }
11
+
12
+ @font-face {
13
+ font-family: 'jm-logo';
14
+ src:url('../font/jm-logo/jm.eot?ycsbky');
15
+ src:url('../font/jm-logo/jm.eot?#iefixycsbky') format('embedded-opentype'),
16
+ url('../font/jm-logo/jm.woff?ycsbky') format('woff'),
17
+ url('../font/jm-logo/jm.ttf?ycsbky') format('truetype'),
18
+ url('../font/jm-logo/jm.svg?ycsbky#icomoon') format('svg');
19
+ font-weight: normal;
20
+ font-style: normal;
21
+ }
22
+
23
+ .jm-icon {
24
+ font-family: "job-manager" !important;
25
+ font-style: normal;
26
+ font-weight: normal;
27
+ speak: none;
28
+
29
+ display: inline-block;
30
+ text-decoration: inherit;
31
+ width: 1em;
32
+ text-align: center;
33
+
34
+ /* For safety - reset parent styles, that can break glyph codes*/
35
+ font-variant: normal;
36
+ text-transform: none;
37
+
38
+ /* fix buttons height, for twitter bootstrap */
39
+ line-height: 1em;
40
+ }
assets/css/job-listings.css DELETED
@@ -1 +0,0 @@
1
- .clearfix{zoom:1}.clearfix:after,.clearfix:before{content:"";display:table}.clearfix:after{clear:both}.rp4wp-related-job_listing>ul,ul.job_listings{padding:0;margin:0;border-top:1px solid #eee}.rp4wp-related-job_listing>ul.loading,ul.job_listings.loading{min-height:96px;border-bottom:1px solid #eee;background:url(../images/ajax-loader.gif) no-repeat center 32px}.rp4wp-related-job_listing>ul li.job_listing,.rp4wp-related-job_listing>ul li.no_job_listings_found,ul.job_listings li.job_listing,ul.job_listings li.no_job_listings_found{list-style:none outside;padding:0;margin:0;border-bottom:1px solid #eee}.rp4wp-related-job_listing>ul li.job_listing.job_position_filled a,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_filled a,ul.job_listings li.job_listing.job_position_filled a,ul.job_listings li.no_job_listings_found.job_position_filled a{opacity:.25}.rp4wp-related-job_listing>ul li.job_listing.no_job_listings_found,.rp4wp-related-job_listing>ul li.no_job_listings_found.no_job_listings_found,ul.job_listings li.job_listing.no_job_listings_found,ul.job_listings li.no_job_listings_found.no_job_listings_found{padding:1em;border-bottom:1px solid #eee}.rp4wp-related-job_listing>ul li.job_listing a,.rp4wp-related-job_listing>ul li.no_job_listings_found a,ul.job_listings li.job_listing a,ul.job_listings li.no_job_listings_found a{display:block;padding:1em 1em 1em 2em;border:0;overflow:hidden;zoom:1;position:relative;line-height:1.5em;text-decoration:none}.rp4wp-related-job_listing>ul li.job_listing a:focus,.rp4wp-related-job_listing>ul li.job_listing a:hover,.rp4wp-related-job_listing>ul li.no_job_listings_found a:focus,.rp4wp-related-job_listing>ul li.no_job_listings_found a:hover,ul.job_listings li.job_listing a:focus,ul.job_listings li.job_listing a:hover,ul.job_listings li.no_job_listings_found a:focus,ul.job_listings li.no_job_listings_found a:hover{background-color:#fcfcfc}.rp4wp-related-job_listing>ul li.job_listing a img.company_logo,.rp4wp-related-job_listing>ul li.no_job_listings_found a img.company_logo,ul.job_listings li.job_listing a img.company_logo,ul.job_listings li.no_job_listings_found a img.company_logo{width:42px;height:42px;position:absolute;left:1em;float:left;margin-right:1em;vertical-align:middle;box-shadow:none}.rp4wp-related-job_listing>ul li.job_listing a div.location,.rp4wp-related-job_listing>ul li.job_listing a div.position,.rp4wp-related-job_listing>ul li.job_listing a ul.meta,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.location,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position,.rp4wp-related-job_listing>ul li.no_job_listings_found a ul.meta,ul.job_listings li.job_listing a div.location,ul.job_listings li.job_listing a div.position,ul.job_listings li.job_listing a ul.meta,ul.job_listings li.no_job_listings_found a div.location,ul.job_listings li.no_job_listings_found a div.position,ul.job_listings li.no_job_listings_found a ul.meta{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.rp4wp-related-job_listing>ul li.job_listing a div.position,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position,ul.job_listings li.job_listing a div.position,ul.job_listings li.no_job_listings_found a div.position{float:left;width:55%;padding:0 0 0 42px;line-height:1.5em}.rp4wp-related-job_listing>ul li.job_listing a div.position h3,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position h3,ul.job_listings li.job_listing a div.position h3,ul.job_listings li.no_job_listings_found a div.position h3{margin:0;padding:0;line-height:inherit;font-size:inherit}.rp4wp-related-job_listing>ul li.job_listing a div.position .company,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position .company,ul.job_listings li.job_listing a div.position .company,ul.job_listings li.no_job_listings_found a div.position .company{color:#999}.rp4wp-related-job_listing>ul li.job_listing a div.position .company .tagline,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.position .company .tagline,ul.job_listings li.job_listing a div.position .company .tagline,ul.job_listings li.no_job_listings_found a div.position .company .tagline{margin-left:.5em}.rp4wp-related-job_listing>ul li.job_listing a div.location,.rp4wp-related-job_listing>ul li.no_job_listings_found a div.location,ul.job_listings li.job_listing a div.location,ul.job_listings li.no_job_listings_found a div.location{float:left;text-align:left;width:25%;padding:0 0 0 1em;color:#999;line-height:1.5em}.rp4wp-related-job_listing>ul li.job_listing a .meta,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta,ul.job_listings li.job_listing a .meta,ul.job_listings li.no_job_listings_found a .meta{float:right;text-align:right;width:20%;padding:0 0 0 1em;margin:0;line-height:1.5em;color:#999;list-style:none outside}.rp4wp-related-job_listing>ul li.job_listing a .meta li,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta li,ul.job_listings li.job_listing a .meta li,ul.job_listings li.no_job_listings_found a .meta li{list-style:none outside;display:block;margin:0}.rp4wp-related-job_listing>ul li.job_listing a .meta .job-type,.rp4wp-related-job_listing>ul li.no_job_listings_found a .meta .job-type,ul.job_listings li.job_listing a .meta .job-type,ul.job_listings li.no_job_listings_found a .meta .job-type{font-weight:700}.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a,ul.job_listings li.job_listing.job_position_featured a,ul.job_listings li.no_job_listings_found.job_position_featured a{background:#fefee5}.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a:focus,.rp4wp-related-job_listing>ul li.job_listing.job_position_featured a:hover,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a:focus,.rp4wp-related-job_listing>ul li.no_job_listings_found.job_position_featured a:hover,ul.job_listings li.job_listing.job_position_featured a:focus,ul.job_listings li.job_listing.job_position_featured a:hover,ul.job_listings li.no_job_listings_found.job_position_featured a:focus,ul.job_listings li.no_job_listings_found.job_position_featured a:hover{background-color:#fefed8}.widget ul.job_listings li.job_listing a{padding:1em 0}.widget ul.job_listings li.job_listing .image{float:left}.widget ul.job_listings li.job_listing .image img{left:0;position:relative}.widget ul.job_listings li.job_listing .content{overflow:hidden}.widget ul.job_listings li.job_listing .position{float:none;width:auto;padding:0}.widget ul.job_listings li.job_listing ul.meta{float:none;width:auto;padding:0;margin:0;text-align:left}.widget ul.job_listings li.job_listing ul.meta li{float:none;display:inline;padding:0;margin:0 .5em 0 0;font-weight:400}.widget ul.job_listings li.job_listing ul.meta li:after{padding:0 0 0 .5em;content:"\2023"}.widget ul.job_listings li.job_listing ul.meta li:last-child:after{content:''}.job-manager .job-type,.job-types .job-type,.job_listing .job-type{color:#f08d3c}.job-manager .full-time,.job-types .full-time,.job_listing .full-time{color:#90da36}.job-manager .part-time,.job-types .part-time,.job_listing .part-time{color:#f08d3c}.job-manager .temporary,.job-types .temporary,.job_listing .temporary{color:#d93674}.job-manager .freelance,.job-types .freelance,.job_listing .freelance{color:#39c}.job-manager .internship,.job-types .internship,.job_listing .internship{color:#6033cc}@media only screen and (max-width:767px){ul.job_listings li.job_listing a,ul.job_listings li.no_job_listings_found a{padding:1em}ul.job_listings li.job_listing a img.company_logo,ul.job_listings li.no_job_listings_found a img.company_logo{visibility:hidden}ul.job_listings li.job_listing a div.position,ul.job_listings li.no_job_listings_found a div.position{float:left;width:60%;padding:0}ul.job_listings li.job_listing a div.location,ul.job_listings li.no_job_listings_found a div.location{float:right;width:40%;line-height:2em;font-size:.75em;padding:0 0 0 1em;text-align:right}ul.job_listings li.job_listing a .meta,ul.job_listings li.no_job_listings_found a .meta{float:right;width:40%;line-height:2em;font-size:.75em}ul.job_listings li.job_listing a .meta li,ul.job_listings li.no_job_listings_found a .meta li{font-size:1em}}.twenty-eleven ul.job_listings li.job_listing,.twenty-eleven ul.job_listings li.no_job_listings_found{padding:0!important}
 
assets/css/job-submission.css DELETED
@@ -1 +0,0 @@
1
- #wp-link #search-panel,#wp-link #wplink-link-existing-content{display:none}div#wp-link-wrap.wp-core-ui{height:300px}.wplink-autocomplete.ui-autocomplete{display:none;visibility:hidden}
 
assets/css/menu.less ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "icons.less";
2
+
3
+ /* Menu */
4
+ #adminmenu {
5
+ #menu-posts-job_listing {
6
+ .wp-menu-image:before {
7
+ content: '\e800';
8
+ .jm-icon;
9
+ }
10
+ }
11
+ #menu-posts-resume {
12
+ .wp-menu-image:before {
13
+ content: '\e806';
14
+ .jm-icon;
15
+ }
16
+ }
17
+ }
assets/css/mixins.less ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @full-time: #90da36;
2
+ @part-time: #f08d3c;
3
+ @internship:#6033cc;
4
+ @freelance: #3399cc;
5
+ @temporary: #d93674;
6
+
7
+ .clearfix {
8
+ zoom: 1; /* For IE 6/7 (trigger hasLayout) */
9
+
10
+ &:before, &:after {
11
+ content: "";
12
+ display: table;
13
+ }
14
+ &:after {
15
+ clear: both;
16
+ }
17
+ }
18
+ .border_radius(@radius:4px) {
19
+ -webkit-border-radius:@radius;
20
+ border-radius:@radius;
21
+ }
22
+ .border_radius_right(@radius:4px) {
23
+ -webkit-border-top-right-radius: @radius;
24
+ -webkit-border-bottom-right-radius: @radius;
25
+ border-top-right-radius: @radius;
26
+ border-bottom-right-radius: @radius;
27
+ }
28
+ .border_radius_left(@radius:4px) {
29
+ -webkit-border-top-left-radius: @radius;
30
+ -webkit-border-bottom-left-radius: @radius;
31
+ border-top-left-radius: @radius;
32
+ border-bottom-left-radius: @radius;
33
+ }
34
+ .border_radius_bottom(@radius:4px) {
35
+ -webkit-border-bottom-left-radius: @radius;
36
+ -webkit-border-bottom-right-radius: @radius;
37
+ border-bottom-left-radius: @radius;
38
+ border-bottom-right-radius: @radius;
39
+ }
40
+ .border_radius_top(@radius:4px) {
41
+ -webkit-border-top-left-radius: @radius;
42
+ -webkit-border-top-right-radius: @radius;
43
+ border-top-left-radius: @radius;
44
+ border-top-right-radius: @radius;
45
+ }
46
+ .opacity(@opacity:0.75) {
47
+ filter:~"alpha(opacity=@opacity * 100)";
48
+ -khtml-opacity: @opacity;
49
+ opacity: @opacity;
50
+ }
51
+ .box_shadow(@shadow_x:3px, @shadow_y:3px, @shadow_rad:3px, @shadow_in:3px, @shadow_color:#888) {
52
+ box-shadow:@shadow_x @shadow_y @shadow_rad @shadow_in @shadow_color;
53
+ -webkit-box-shadow:@shadow_x @shadow_y @shadow_rad @shadow_in @shadow_color;
54
+ -moz-box-shadow:@shadow_x @shadow_y @shadow_rad @shadow_in @shadow_color;
55
+ }
56
+ .inset_box_shadow(@shadow_x:3px, @shadow_y:3px, @shadow_rad:3px, @shadow_in:3px, @shadow_color:#888) {
57
+ box-shadow:inset @shadow_x @shadow_y @shadow_rad @shadow_in @shadow_color;
58
+ -webkit-box-shadow:inset @shadow_x @shadow_y @shadow_rad @shadow_in @shadow_color;
59
+ -moz-box-shadow:inset @shadow_x @shadow_y @shadow_rad @shadow_in @shadow_color;
60
+ }
61
+ .text_shadow(@shadow_x:3px, @shadow_y:3px, @shadow_rad:3px, @shadow_color:#fff) {
62
+ text-shadow:@shadow_x @shadow_y @shadow_rad @shadow_color;
63
+ }
64
+ .vertical_gradient(@from: #000, @to: #FFF) {
65
+ background: @from;
66
+ background: -webkit-gradient(linear, left top, left bottom, from(@from), to(@to));
67
+ background: -webkit-linear-gradient(@from, @to);
68
+ background: -moz-linear-gradient(center top, @from 0%, @to 100%);
69
+ background: -moz-gradient(center top, @from 0%, @to 100%);
70
+ }
71
+ .transition(@selector:all, @animation:ease-in-out, @duration:.2s) {
72
+ -webkit-transition:@selector @animation @duration;
73
+ -moz-transition:@selector @animation @duration;
74
+ -o-transition:@selector @animation @duration;
75
+ transition:@selector @animation @duration;
76
+ }
77
+ .scale(@ratio:1.5){
78
+ -webkit-transform:scale(@ratio);
79
+ -moz-transform:scale(@ratio);
80
+ -ms-transform:scale(@ratio);
81
+ -o-transform:scale(@ratio);
82
+ transform:scale(@ratio);
83
+ }
84
+ .borderbox () {
85
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
86
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
87
+ box-sizing: border-box; /* Opera/IE 8+ */
88
+ }
89
+ .darkorlighttextshadow ( @a, @opacity: 0.8 ) when (lightness(@a) >= 65%) { .text_shadow( 0, -1px, 0, rgba(0,0,0,@opacity) ); }
90
+ .darkorlighttextshadow ( @a, @opacity: 0.8 ) when (lightness(@a) < 65%) { .text_shadow( 0, 1px, 0, rgba(255,255,255,@opacity) ); }
assets/css/setup.less ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .help-page-link {
2
+ cursor: help;
3
+ }
4
+
5
+ .wp-job-manager-setup-steps {
6
+ margin: 1em 0 2em;
7
+ overflow: hidden;
8
+ li {
9
+ width: 33.3%;
10
+ padding: 7px 1em;
11
+ font-weight: bold;
12
+ margin: 0;
13
+ color: #eee;
14
+ background: #222222;
15
+ float: left;
16
+ -moz-box-sizing: border-box;
17
+ -webkit-box-sizing: border-box;
18
+ box-sizing: border-box;
19
+
20
+ &:first-child {
21
+ margin-left: 0;
22
+ }
23
+ &.wp-job-manager-setup-active-step {
24
+ background: #0074a2;
25
+ color: #eee;
26
+ }
27
+ }
28
+ }
29
+
30
+ .wp-job-manager-shortcodes {
31
+ td, th {
32
+ vertical-align: middle;
33
+ p {
34
+ margin: 9px 0;
35
+ }
36
+ }
37
+ tr:nth-child(even) {
38
+ td, th {
39
+ background: #f9f9f9;
40
+ }
41
+ }
42
+ }
43
+
44
+ .wp-job-manager-next-steps {
45
+ font-size: 1.1em;
46
+ list-style: disc inside;
47
+ margin: 1.5em 2em;
48
+ }
49
+
50
+ .wp-job-manager-support-the-plugin {
51
+ background: #fff;
52
+ padding: 2em;
53
+ margin: 2em 0;
54
+ h3 {
55
+ margin-top: 0;
56
+ }
57
+ ul {
58
+ margin-bottom: 0;
59
+ }
60
+ li {
61
+ line-height: 2em;
62
+ font-size: 1.1em;
63
+ a {
64
+ text-decoration: none;
65
+ }
66
+ }
67
+ li a:before {
68
+ font-family: "dashicons";
69
+ font-size: 2em;
70
+ vertical-align: middle;
71
+ padding-right: .25em;
72
+ }
73
+ li.icon-review {
74
+ a:before {
75
+ content: "\f155";
76
+ }
77
+ }
78
+ li.icon-localization {
79
+ a:before {
80
+ content: "\f319";
81
+ }
82
+ }
83
+ li.icon-code {
84
+ a:before {
85
+ content: "\f111";
86
+ }
87
+ }
88
+ li.icon-forum {
89
+ a:before {
90
+ content: "\f328";
91
+ }
92
+ }
93
+ }
assets/js/admin.js ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(document).ready(function($) {
2
+ // Tooltips
3
+ $( ".tips, .help_tip" ).tipTip({
4
+ 'attribute' : 'data-tip',
5
+ 'fadeIn' : 50,
6
+ 'fadeOut' : 50,
7
+ 'delay' : 200
8
+ });
9
+
10
+ // Author
11
+ $( "p.form-field-author" ).on( 'click', 'a.change-author', function() {
12
+ $(this).closest( 'p' ).find('.current-author').hide();
13
+ $(this).closest( 'p' ).find('.change-author').show();
14
+ return false;
15
+ });
16
+
17
+ // Datepicker
18
+ $( "input#_job_expires" ).datepicker({
19
+ altFormat : 'yy-mm-dd',
20
+ dateFormat : job_manager_admin.date_format,
21
+ minDate : 0
22
+ });
23
+
24
+ if ( $( "input#_job_expires" ).val() ) {
25
+ var date = new Date( $( "input#_job_expires" ).val() );
26
+ $( "input#_job_expires" ).datepicker( "setDate", date );
27
+ }
28
+
29
+ // Uploading files
30
+ var file_frame;
31
+ var file_target_input;
32
+ var file_target_wrapper;
33
+
34
+ $('.wp_job_manager_add_another_file_button').live('click', function( event ){
35
+ event.preventDefault();
36
+
37
+ var wrapper = $( this ).closest( '.form-field' );
38
+ var field_name = $( this ).data( 'field_name' );
39
+ var field_placeholder = $( this ).data( 'field_placeholder' );
40
+ var button_text = $( this ).data( 'uploader_button_text' );
41
+ var button = $( this ).data( 'uploader_button' );
42
+
43
+ $( 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></span>');
44
+ });
45
+
46
+ $('.wp_job_manager_upload_file_button').live('click', function( event ){
47
+ event.preventDefault();
48
+
49
+ file_target_wrapper = $( this ).closest('.file_url');
50
+ file_target_input = file_target_wrapper.find('input');
51
+
52
+ // If the media frame already exists, reopen it.
53
+ if ( file_frame ) {
54
+ file_frame.open();
55
+ return;
56
+ }
57
+
58
+ // Create the media frame.
59
+ file_frame = wp.media.frames.file_frame = wp.media({
60
+ title: $( this ).data( 'uploader_title' ),
61
+ button: {
62
+ text: $( this ).data( 'uploader_button_text' ),
63
+ },
64
+ multiple: false // Set to true to allow multiple files to be selected
65
+ });
66
+
67
+ // When an image is selected, run a callback.
68
+ file_frame.on( 'select', function() {
69
+ // We set multiple to false so only get one image from the uploader
70
+ attachment = file_frame.state().get('selection').first().toJSON();
71
+
72
+ $( file_target_input ).val( attachment.url );
73
+ });
74
+
75
+ // Finally, open the modal
76
+ file_frame.open();
77
+ });
78
+ });
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,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)})});
1
+ jQuery(document).ready(function(a){if(a(".tips, .help_tip").tipTip({attribute:"data-tip",fadeIn:50,fadeOut:50,delay:200}),a("p.form-field-author").on("click","a.change-author",function(){return a(this).closest("p").find(".current-author").hide(),a(this).closest("p").find(".change-author").show(),!1}),a("input#_job_expires").datepicker({altFormat:"yy-mm-dd",dateFormat:job_manager_admin.date_format,minDate:0}),a("input#_job_expires").val()){var b=new Date(a("input#_job_expires").val());a("input#_job_expires").datepicker("setDate",b),a("input#_job_expires").attr("placeholder",job_manager_admin.date_format)}var c,d,e;a(".wp_job_manager_add_another_file_button").live("click",function(b){b.preventDefault();var c=(a(this).closest(".form-field"),a(this).data("field_name")),d=a(this).data("field_placeholder"),e=a(this).data("uploader_button_text"),f=a(this).data("uploader_button");a(this).before('<span class="file_url"><input type="text" name="'+c+'[]" placeholder="'+d+'" /><button class="button button-small wp_job_manager_upload_file_button" data-uploader_button_text="'+e+'">'+f+"</button></span>")}),a(".wp_job_manager_upload_file_button").live("click",function(b){return b.preventDefault(),e=a(this).closest(".file_url"),d=e.find("input"),c?void c.open():(c=wp.media.frames.file_frame=wp.media({title:a(this).data("uploader_title"),button:{text:a(this).data("uploader_button_text")},multiple:!1}),c.on("select",function(){attachment=c.state().get("selection").first().toJSON(),a(d).val(attachment.url)}),void c.open())})});
assets/js/ajax-file-upload.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(function($) {
2
+ $('.wp-job-manager-file-upload').each(function(){
3
+ $(this).fileupload({
4
+ dataType: 'json',
5
+ dropZone: $(this),
6
+ url: job_manager_ajax_file_upload.ajax_url.toString().replace( "%%endpoint%%", "upload_file" ),
7
+ maxNumberOfFiles: 1,
8
+ formData: {
9
+ script: true
10
+ },
11
+ add: function (e, data) {
12
+ var $file_field = $( this );
13
+ var $form = $file_field.closest( 'form' );
14
+ var $uploaded_files = $file_field.parent().find('.job-manager-uploaded-files');
15
+ var uploadErrors = [];
16
+
17
+ // Validate type
18
+ var allowed_types = $(this).data('file_types');
19
+
20
+ if ( allowed_types ) {
21
+ var acceptFileTypes = new RegExp( "(\.|\/)(" + allowed_types + ")$", "i" );
22
+
23
+ if ( data.originalFiles[0]['name'].length && ! acceptFileTypes.test( data.originalFiles[0]['name'] ) ) {
24
+ uploadErrors.push( job_manager_ajax_file_upload.i18n_invalid_file_type + ' ' + allowed_types );
25
+ }
26
+ }
27
+
28
+ if ( uploadErrors.length > 0 ) {
29
+ alert( uploadErrors.join( "\n" ) );
30
+ } else {
31
+ $form.find(':input[type="submit"]').attr( 'disabled', 'disabled' );
32
+ data.context = $('<progress value="" max="100"></progress>').appendTo( $uploaded_files );
33
+ data.submit();
34
+ }
35
+ },
36
+ progress: function (e, data) {
37
+ var $file_field = $( this );
38
+ var $uploaded_files = $file_field.parent().find('.job-manager-uploaded-files');
39
+ var progress = parseInt(data.loaded / data.total * 100, 10);
40
+ data.context.val( progress );
41
+ },
42
+ fail: function (e, data) {
43
+ var $file_field = $( this );
44
+ var $form = $file_field.closest( 'form' );
45
+
46
+ if ( data.errorThrown ) {
47
+ alert( data.errorThrown );
48
+ }
49
+
50
+ data.context.remove();
51
+
52
+ $form.find(':input[type="submit"]').removeAttr( 'disabled' );
53
+ },
54
+ done: function (e, data) {
55
+ var $file_field = $( this );
56
+ var $form = $file_field.closest( 'form' );
57
+ var $uploaded_files = $file_field.parent().find('.job-manager-uploaded-files');
58
+ var multiple = $file_field.attr( 'multiple' ) ? 1 : 0;
59
+ var image_types = [ 'jpg', 'gif', 'png', 'jpeg', 'jpe' ];
60
+
61
+ data.context.remove();
62
+
63
+ $.each(data.result.files, function(index, file) {
64
+ if ( file.error ) {
65
+ alert( file.error );
66
+ } else {
67
+ if ( $.inArray( file.extension, image_types ) >= 0 ) {
68
+ var html = $.parseHTML( job_manager_ajax_file_upload.js_field_html_img );
69
+ $( html ).find('.job-manager-uploaded-file-preview img').attr( 'src', file.url );
70
+ } else {
71
+ var html = $.parseHTML( job_manager_ajax_file_upload.js_field_html );
72
+ $( html ).find('.job-manager-uploaded-file-name code').text( file.name );
73
+ }
74
+
75
+ $( html ).find('.input-text').val( file.url );
76
+ $( html ).find('.input-text').attr( 'name', 'current_' + $file_field.attr( 'name' ) );
77
+
78
+ if ( multiple ) {
79
+ $uploaded_files.append( html );
80
+ } else {
81
+ $uploaded_files.html( html );
82
+ }
83
+ }
84
+ });
85
+
86
+ $form.find(':input[type="submit"]').removeAttr( 'disabled' );
87
+ }
88
+ });
89
+ });
90
+ });
assets/js/ajax-file-upload.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(function(e){e(".wp-job-manager-file-upload").each(function(){e(this).fileupload({dataType:"json",dropZone:e(this),url:job_manager_ajax_file_upload.ajax_url.toString().replace("%%endpoint%%","upload_file"),maxNumberOfFiles:1,formData:{script:!0},add:function(a,t){var i=e(this),r=i.closest("form"),n=i.parent().find(".job-manager-uploaded-files"),o=[],l=e(this).data("file_types");if(l){var s=new RegExp("(.|/)("+l+")$","i");t.originalFiles[0].name.length&&!s.test(t.originalFiles[0].name)&&o.push(job_manager_ajax_file_upload.i18n_invalid_file_type+" "+l)}o.length>0?window.alert(o.join("\n")):(r.find(':input[type="submit"]').attr("disabled","disabled"),t.context=e('<progress value="" max="100"></progress>').appendTo(n),t.submit())},progress:function(e,a){var t=parseInt(a.loaded/a.total*100,10);a.context.val(t)},fail:function(a,t){var i=e(this).closest("form");t.errorThrown&&window.alert(t.errorThrown),t.context.remove(),i.find(':input[type="submit"]').removeAttr("disabled")},done:function(a,t){var i=e(this),r=i.closest("form"),n=i.parent().find(".job-manager-uploaded-files"),o=i.attr("multiple")?1:0,l=["jpg","gif","png","jpeg","jpe"];t.context.remove(),void 0===t.result.success||t.result.success||window.alert(t.result.data),e.each(t.result.files,function(a,t){if(t.error)window.alert(t.error);else{var r;e.inArray(t.extension,l)>=0?(r=e.parseHTML(job_manager_ajax_file_upload.js_field_html_img),e(r).find(".job-manager-uploaded-file-preview img").attr("src",t.url)):(r=e.parseHTML(job_manager_ajax_file_upload.js_field_html),e(r).find(".job-manager-uploaded-file-name code").text(t.name)),e(r).find(".input-text").val(t.url),e(r).find(".input-text").attr("name","current_"+i.attr("name")),o?n.append(r):n.html(r)}}),r.find(':input[type="submit"]').removeAttr("disabled")}})})});
1
+ jQuery(function(a){a(".wp-job-manager-file-upload").each(function(){a(this).fileupload({dataType:"json",dropZone:a(this),url:job_manager_ajax_file_upload.ajax_url.toString().replace("%%endpoint%%","upload_file"),maxNumberOfFiles:1,formData:{script:!0},add:function(b,c){var d=a(this),e=d.closest("form"),f=d.parent().find(".job-manager-uploaded-files"),g=[],h=a(this).data("file_types");if(h){var i=new RegExp("(.|/)("+h+")$","i");c.originalFiles[0].name.length&&!i.test(c.originalFiles[0].name)&&g.push(job_manager_ajax_file_upload.i18n_invalid_file_type+" "+h)}g.length>0?alert(g.join("\n")):(e.find(':input[type="submit"]').attr("disabled","disabled"),c.context=a('<progress value="" max="100"></progress>').appendTo(f),c.submit())},progress:function(b,c){var d=a(this),e=(d.parent().find(".job-manager-uploaded-files"),parseInt(c.loaded/c.total*100,10));c.context.val(e)},fail:function(b,c){var d=a(this),e=d.closest("form");c.errorThrown&&alert(c.errorThrown),c.context.remove(),e.find(':input[type="submit"]').removeAttr("disabled")},done:function(b,c){var d=a(this),e=d.closest("form"),f=d.parent().find(".job-manager-uploaded-files"),g=d.attr("multiple")?1:0,h=["jpg","gif","png","jpeg","jpe"];c.context.remove(),a.each(c.result.files,function(b,c){if(c.error)alert(c.error);else{if(a.inArray(c.extension,h)>=0){var e=a.parseHTML(job_manager_ajax_file_upload.js_field_html_img);a(e).find(".job-manager-uploaded-file-preview img").attr("src",c.url)}else{var e=a.parseHTML(job_manager_ajax_file_upload.js_field_html);a(e).find(".job-manager-uploaded-file-name code").text(c.name)}a(e).find(".input-text").val(c.url),a(e).find(".input-text").attr("name","current_"+d.attr("name")),g?f.append(e):f.html(e)}}),e.find(':input[type="submit"]').removeAttr("disabled")}})})});
assets/js/ajax-filters.js ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery( document ).ready( function ( $ ) {
2
+
3
+ var xhr = [];
4
+
5
+ $( '.job_listings' ).on( 'update_results', function ( event, page, append, loading_previous ) {
6
+ var data = '';
7
+ var target = $( this );
8
+ var form = target.find( '.job_filters' );
9
+ var showing = target.find( '.showing_jobs' );
10
+ var results = target.find( '.job_listings' );
11
+ var per_page = target.data( 'per_page' );
12
+ var orderby = target.data( 'orderby' );
13
+ var order = target.data( 'order' );
14
+ var featured = target.data( 'featured' );
15
+ var filled = target.data( 'filled' );
16
+ var index = $( 'div.job_listings' ).index(this);
17
+
18
+ if ( index < 0 ) {
19
+ return;
20
+ }
21
+
22
+ if ( xhr[index] ) {
23
+ xhr[index].abort();
24
+ }
25
+
26
+ if ( ! append ) {
27
+ $( results ).addClass( 'loading' );
28
+ $( 'li.job_listing, li.no_job_listings_found', results ).css( 'visibility', 'hidden' );
29
+
30
+ // Not appending. If page > 1, we should show a load previous button so the user can get to earlier-page listings if needed
31
+ if ( page > 1 && true != target.data( 'show_pagination' ) ) {
32
+ $( results ).before( '<a class="load_more_jobs load_previous" href="#"><strong>' + job_manager_ajax_filters.i18n_load_prev_listings + '</strong></a>' );
33
+ } else {
34
+ target.find( '.load_previous' ).remove();
35
+ }
36
+
37
+ target.find( '.load_more_jobs' ).data( 'page', page );
38
+ }
39
+
40
+ if ( true == target.data( 'show_filters' ) ) {
41
+
42
+ var filter_job_type = [];
43
+
44
+ $( ':input[name="filter_job_type[]"]:checked, :input[name="filter_job_type[]"][type="hidden"], :input[name="filter_job_type"]', form ).each( function () {
45
+ filter_job_type.push( $( this ).val() );
46
+ } );
47
+
48
+ var categories = form.find( ':input[name^="search_categories"]' ).map( function () {
49
+ return $( this ).val();
50
+ } ).get();
51
+ var keywords = '';
52
+ var location = '';
53
+ var $keywords = form.find( ':input[name="search_keywords"]' );
54
+ var $location = form.find( ':input[name="search_location"]' );
55
+
56
+ // Workaround placeholder scripts
57
+ if ( $keywords.val() !== $keywords.attr( 'placeholder' ) ) {
58
+ keywords = $keywords.val();
59
+ }
60
+
61
+ if ( $location.val() !== $location.attr( 'placeholder' ) ) {
62
+ location = $location.val();
63
+ }
64
+
65
+ data = {
66
+ lang: job_manager_ajax_filters.lang,
67
+ search_keywords: keywords,
68
+ search_location: location,
69
+ search_categories: categories,
70
+ filter_job_type: filter_job_type,
71
+ per_page: per_page,
72
+ orderby: orderby,
73
+ order: order,
74
+ page: page,
75
+ featured: featured,
76
+ filled: filled,
77
+ show_pagination: target.data( 'show_pagination' ),
78
+ form_data: form.serialize()
79
+ };
80
+
81
+ } else {
82
+
83
+ var categories = target.data( 'categories' );
84
+ var keywords = target.data( 'keywords' );
85
+ var location = target.data( 'location' );
86
+
87
+ if ( categories ) {
88
+ categories = categories.split( ',' );
89
+ }
90
+
91
+ data = {
92
+ lang: job_manager_ajax_filters.lang,
93
+ search_categories: categories,
94
+ search_keywords: keywords,
95
+ search_location: location,
96
+ per_page: per_page,
97
+ orderby: orderby,
98
+ order: order,
99
+ page: page,
100
+ featured: featured,
101
+ filled: filled,
102
+ show_pagination: target.data( 'show_pagination' )
103
+ };
104
+
105
+ }
106
+
107
+ xhr[index] = $.ajax( {
108
+ type: 'POST',
109
+ url: job_manager_ajax_filters.ajax_url.toString().replace( "%%endpoint%%", "get_listings" ),
110
+ data: data,
111
+ success: function ( result ) {
112
+ if ( result ) {
113
+ try {
114
+ if ( result.showing ) {
115
+ $( showing ).show().html( '<span>' + result.showing + '</span>' + result.showing_links );
116
+ } else {
117
+ $( showing ).hide();
118
+ }
119
+
120
+ if ( result.showing_all ) {
121
+ $( showing ).addClass( 'wp-job-manager-showing-all' );
122
+ } else {
123
+ $( showing ).removeClass( 'wp-job-manager-showing-all' );
124
+ }
125
+
126
+ if ( result.html ) {
127
+ if ( append && loading_previous ) {
128
+ $( results ).prepend( result.html );
129
+ } else if ( append ) {
130
+ $( results ).append( result.html );
131
+ } else {
132
+ $( results ).html( result.html );
133
+ }
134
+ }
135
+
136
+ if ( true == target.data( 'show_pagination' ) ) {
137
+ target.find('.job-manager-pagination').remove();
138
+
139
+ if ( result.pagination ) {
140
+ target.append( result.pagination );
141
+ }
142
+ } else {
143
+ if ( ! result.found_jobs || result.max_num_pages <= page ) {
144
+ $( '.load_more_jobs:not(.load_previous)', target ).hide();
145
+ } else if ( ! loading_previous ) {
146
+ $( '.load_more_jobs', target ).show();
147
+ }
148
+ $( '.load_more_jobs', target ).removeClass( 'loading' );
149
+ $( 'li.job_listing', results ).css( 'visibility', 'visible' );
150
+ }
151
+
152
+ $( results ).removeClass( 'loading' );
153
+
154
+ target.triggerHandler( 'updated_results', result );
155
+
156
+ } catch ( err ) {
157
+ if ( window.console ) {
158
+ console.log( err );
159
+ }
160
+ }
161
+ }
162
+ },
163
+ error: function ( jqXHR, textStatus, error ) {
164
+ if ( window.console && 'abort' !== textStatus ) {
165
+ console.log( textStatus + ': ' + error );
166
+ }
167
+ },
168
+ statusCode: {
169
+ 404: function() {
170
+ if ( window.console ) {
171
+ console.log( "Error 404: Ajax Endpoint cannot be reached. Go to Settings > Permalinks and save to resolve." );
172
+ }
173
+ }
174
+ }
175
+ } );
176
+ } );
177
+
178
+ $( '#search_keywords, #search_location, .job_types :input, #search_categories, .job-manager-filter' ).change( function() {
179
+ var target = $( this ).closest( 'div.job_listings' );
180
+ target.triggerHandler( 'update_results', [ 1, false ] );
181
+ job_manager_store_state( target, 1 );
182
+ } )
183
+
184
+ .on( "keyup", function(e) {
185
+ if ( e.which === 13 ) {
186
+ $( this ).trigger( 'change' );
187
+ }
188
+ } );
189
+
190
+ $( '.job_filters' ).on( 'click', '.reset', function () {
191
+ var target = $( this ).closest( 'div.job_listings' );
192
+ var form = $( this ).closest( 'form' );
193
+
194
+ form.find( ':input[name="search_keywords"], :input[name="search_location"], .job-manager-filter' ).not(':input[type="hidden"]').val( '' ).trigger( 'chosen:updated' );
195
+ form.find( ':input[name^="search_categories"]' ).not(':input[type="hidden"]').val( 0 ).trigger( 'chosen:updated' );
196
+ $( ':input[name="filter_job_type[]"]', form ).not(':input[type="hidden"]').attr( 'checked', 'checked' );
197
+
198
+ target.triggerHandler( 'reset' );
199
+ target.triggerHandler( 'update_results', [ 1, false ] );
200
+ job_manager_store_state( target, 1 );
201
+
202
+ return false;
203
+ } );
204
+
205
+ $( document.body ).on( 'click', '.load_more_jobs', function() {
206
+ var target = $( this ).closest( 'div.job_listings' );
207
+ var page = parseInt( $( this ).data( 'page' ) || 1 );
208
+ var loading_previous = false;
209
+
210
+ $(this).addClass( 'loading' );
211
+
212
+ if ( $(this).is('.load_previous') ) {
213
+ page = page - 1;
214
+ loading_previous = true;
215
+ if ( page === 1 ) {
216
+ $(this).remove();
217
+ } else {
218
+ $( this ).data( 'page', page );
219
+ }
220
+ } else {
221
+ page = page + 1;
222
+ $( this ).data( 'page', page );
223
+ job_manager_store_state( target, page );
224
+ }
225
+
226
+ target.triggerHandler( 'update_results', [ page, true, loading_previous ] );
227
+ return false;
228
+ } );
229
+
230
+ $( 'div.job_listings' ).on( 'click', '.job-manager-pagination a', function() {
231
+ var target = $( this ).closest( 'div.job_listings' );
232
+ var page = $( this ).data( 'page' );
233
+
234
+ job_manager_store_state( target, page );
235
+
236
+ target.triggerHandler( 'update_results', [ page, false ] );
237
+
238
+ $( "body, html" ).animate({
239
+ scrollTop: target.offset().top
240
+ }, 600 );
241
+
242
+ return false;
243
+ } );
244
+
245
+ if ( $.isFunction( $.fn.chosen ) ) {
246
+ if ( job_manager_ajax_filters.is_rtl == 1 ) {
247
+ $( 'select[name^="search_categories"]' ).addClass( 'chosen-rtl' );
248
+ }
249
+ $( 'select[name^="search_categories"]' ).chosen({ search_contains: true });
250
+ }
251
+
252
+ if ( window.history && window.history.pushState ) {
253
+ $supports_html5_history = true;
254
+ } else {
255
+ $supports_html5_history = false;
256
+ }
257
+
258
+ var location = document.location.href.split('#')[0];
259
+
260
+ function job_manager_store_state( target, page ) {
261
+ if ( $supports_html5_history ) {
262
+ var form = target.find( '.job_filters' );
263
+ var data = $( form ).serialize();
264
+ var index = $( 'div.job_listings' ).index( target );
265
+ window.history.replaceState( { id: 'job_manager_state', page: page, data: data, index: index }, '', location + '#s=1' );
266
+ }
267
+ }
268
+
269
+ // Inital job and form population
270
+ $(window).on( "load", function( event ) {
271
+ $( '.job_filters' ).each( function() {
272
+ var target = $( this ).closest( 'div.job_listings' );
273
+ var form = target.find( '.job_filters' );
274
+ var inital_page = 1;
275
+ var index = $( 'div.job_listings' ).index( target );
276
+
277
+ if ( window.history.state && window.location.hash ) {
278
+ var state = window.history.state;
279
+ if ( state.id && 'job_manager_state' === state.id && index == state.index ) {
280
+ inital_page = state.page;
281
+ form.deserialize( state.data );
282
+ form.find( ':input[name^="search_categories"]' ).not(':input[type="hidden"]').trigger( 'chosen:updated' );
283
+ }
284
+ }
285
+
286
+ target.triggerHandler( 'update_results', [ inital_page, false ] );
287
+ });
288
+ });
289
+ } );
assets/js/ajax-filters.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(document).ready(function(e){function a(a,t){if(i){var n=a.find(".job_filters"),s=e(n).serialize(),r=e("div.job_listings").index(a);window.history.replaceState({id:"job_manager_state",page:t,data:s,index:r},"",o+"#s=1")}}var t=[];e(".job_listings").on("update_results",function(a,i,o,n){var s,r,d,l="",_=e(this),g=_.find(".job_filters"),c=_.find(".showing_jobs"),p=_.find(".job_listings"),h=_.data("per_page"),u=_.data("orderby"),f=_.data("order"),b=_.data("featured"),j=_.data("filled"),m=_.data("job_types"),w=_.data("post_status"),v=e("div.job_listings").index(this);if(!(v<0)){if(t[v]&&t[v].abort(),!o){if(e(p).addClass("loading"),e("li.job_listing, li.no_job_listings_found",p).css("visibility","hidden"),i>1&&!0!==_.data("show_pagination")){var y=jQuery("<strong>").text(job_manager_ajax_filters.i18n_load_prev_listings).wrap('<a class="load_more_jobs load_previous" href="#"></a>');e(p).before(y)}else _.find(".load_previous").remove();_.find(".load_more_jobs").data("page",i)}if(!0===_.data("show_filters")){var k=[];e(':input[name="filter_job_type[]"]:checked, :input[name="filter_job_type[]"][type="hidden"], :input[name="filter_job_type"]',g).each(function(){k.push(e(this).val())}),s=g.find(':input[name^="search_categories"]').map(function(){return e(this).val()}).get(),r="",d="";var x=g.find(':input[name="search_keywords"]'),C=g.find(':input[name="search_location"]');x.val()!==x.attr("placeholder")&&(r=x.val()),C.val()!==C.attr("placeholder")&&(d=C.val()),l={lang:job_manager_ajax_filters.lang,search_keywords:r,search_location:d,search_categories:s,filter_job_type:k,filter_post_status:w,per_page:h,orderby:u,order:f,page:i,featured:b,filled:j,show_pagination:_.data("show_pagination"),form_data:g.serialize()}}else s=_.data("categories"),r=_.data("keywords"),d=_.data("location"),s&&("string"!=typeof s&&(s=String(s)),s=s.split(",")),l={lang:job_manager_ajax_filters.lang,search_categories:s,search_keywords:r,search_location:d,filter_post_status:w,filter_job_type:m,per_page:h,orderby:u,order:f,page:i,featured:b,filled:j,show_pagination:_.data("show_pagination")};t[v]=e.ajax({type:"POST",url:job_manager_ajax_filters.ajax_url.toString().replace("%%endpoint%%","get_listings"),data:l,success:function(a){if(a)try{if(a.showing){var t=jQuery("<span>").html(a.showing);e(c).show().html("").html(a.showing_links).prepend(t)}else e(c).hide();a.showing_all?e(c).addClass("wp-job-manager-showing-all"):e(c).removeClass("wp-job-manager-showing-all"),a.html&&(o&&n?e(p).prepend(a.html):o?e(p).append(a.html):e(p).html(a.html)),!0===_.data("show_pagination")?(_.find(".job-manager-pagination").remove(),a.pagination&&_.append(a.pagination)):(!a.found_jobs||a.max_num_pages<=i?e(".load_more_jobs:not(.load_previous)",_).hide():n||e(".load_more_jobs",_).show(),e(".load_more_jobs",_).removeClass("loading"),e("li.job_listing",p).css("visibility","visible")),e(p).removeClass("loading"),_.triggerHandler("updated_results",a)}catch(e){window.console&&window.console.log(e)}},error:function(e,a,t){window.console&&"abort"!==a&&window.console.log(a+": "+t)},statusCode:{404:function(){window.console&&window.console.log("Error 404: Ajax Endpoint cannot be reached. Go to Settings > Permalinks and save to resolve.")}}})}}),e("#search_keywords, #search_location, .job_types :input, #search_categories, .job-manager-filter").change(function(){var t=e(this).closest("div.job_listings");t.triggerHandler("update_results",[1,!1]),a(t,1)}).on("keyup",function(a){13===a.which&&e(this).trigger("change")}),e(".job_filters").on("click",".reset",function(){var t=e(this).closest("div.job_listings"),i=e(this).closest("form");return i.find(':input[name="search_keywords"], :input[name="search_location"], .job-manager-filter').not(':input[type="hidden"]').val("").trigger("chosen:updated"),i.find(':input[name^="search_categories"]').not(':input[type="hidden"]').val("").trigger("chosen:updated"),e(':input[name="filter_job_type[]"]',i).not(':input[type="hidden"]').attr("checked","checked"),t.triggerHandler("reset"),t.triggerHandler("update_results",[1,!1]),a(t,1),!1}),e(document.body).on("click",".load_more_jobs",function(){var t=e(this).closest("div.job_listings"),i=parseInt(e(this).data("page")||1,10),o=!1;return e(this).addClass("loading"),e(this).is(".load_previous")?(o=!0,1===(i-=1)?e(this).remove():e(this).data("page",i)):(i+=1,e(this).data("page",i),a(t,i)),t.triggerHandler("update_results",[i,!0,o]),!1}),e("div.job_listings").on("click",".job-manager-pagination a",function(){var t=e(this).closest("div.job_listings"),i=e(this).data("page");return a(t,i),t.triggerHandler("update_results",[i,!1]),e("body, html").animate({scrollTop:t.offset().top},600),!1}),e.isFunction(e.fn.chosen)&&(1===job_manager_ajax_filters.is_rtl&&e('select[name^="search_categories"]').addClass("chosen-rtl"),e('select[name^="search_categories"]').chosen({search_contains:!0}));var i=!1;window.history&&window.history.pushState&&(i=!0);var o=document.location.href.split("#")[0];e(window).on("load",function(){e(".job_filters").each(function(){var a=e(this).closest("div.job_listings"),t=a.find(".job_filters"),i=1,o=e("div.job_listings").index(a);if(window.history.state&&window.location.hash){var n=window.history.state;n.id&&"job_manager_state"===n.id&&o===n.index&&(i=n.page,t.deserialize(n.data),t.find(':input[name^="search_categories"]').not(':input[type="hidden"]').trigger("chosen:updated"))}a.triggerHandler("update_results",[i,!1])})})});
1
+ jQuery(document).ready(function(a){function b(b,c){if($supports_html5_history){var e=b.find(".job_filters"),f=a(e).serialize(),g=a("div.job_listings").index(b);window.history.replaceState({id:"job_manager_state",page:c,data:f,index:g},"",d+"#s=1")}}var c=[];a(".job_listings").on("update_results",function(b,d,e,f){var g="",h=a(this),i=h.find(".job_filters"),j=h.find(".showing_jobs"),k=h.find(".job_listings"),l=h.data("per_page"),m=h.data("orderby"),n=h.data("order"),o=h.data("featured"),p=h.data("filled"),q=a("div.job_listings").index(this);if(!(0>q)){if(c[q]&&c[q].abort(),e||(a(k).addClass("loading"),a("li.job_listing, li.no_job_listings_found",k).css("visibility","hidden"),d>1&&1!=h.data("show_pagination")?a(k).before('<a class="load_more_jobs load_previous" href="#"><strong>'+job_manager_ajax_filters.i18n_load_prev_listings+"</strong></a>"):h.find(".load_previous").remove(),h.find(".load_more_jobs").data("page",d)),1==h.data("show_filters")){var r=[];a(':input[name="filter_job_type[]"]:checked, :input[name="filter_job_type[]"][type="hidden"], :input[name="filter_job_type"]',i).each(function(){r.push(a(this).val())});var s=i.find(':input[name^="search_categories"]').map(function(){return a(this).val()}).get(),t="",u="",v=i.find(':input[name="search_keywords"]'),w=i.find(':input[name="search_location"]');v.val()!==v.attr("placeholder")&&(t=v.val()),w.val()!==w.attr("placeholder")&&(u=w.val()),g={lang:job_manager_ajax_filters.lang,search_keywords:t,search_location:u,search_categories:s,filter_job_type:r,per_page:l,orderby:m,order:n,page:d,featured:o,filled:p,show_pagination:h.data("show_pagination"),form_data:i.serialize()}}else{var s=h.data("categories"),t=h.data("keywords"),u=h.data("location");s&&(s=s.split(",")),g={lang:job_manager_ajax_filters.lang,search_categories:s,search_keywords:t,search_location:u,per_page:l,orderby:m,order:n,page:d,featured:o,filled:p,show_pagination:h.data("show_pagination")}}c[q]=a.ajax({type:"POST",url:job_manager_ajax_filters.ajax_url.toString().replace("%%endpoint%%","get_listings"),data:g,success:function(b){if(b)try{b.showing?a(j).show().html("<span>"+b.showing+"</span>"+b.showing_links):a(j).hide(),b.showing_all?a(j).addClass("wp-job-manager-showing-all"):a(j).removeClass("wp-job-manager-showing-all"),b.html&&(e&&f?a(k).prepend(b.html):e?a(k).append(b.html):a(k).html(b.html)),1==h.data("show_pagination")?(h.find(".job-manager-pagination").remove(),b.pagination&&h.append(b.pagination)):(!b.found_jobs||b.max_num_pages<=d?a(".load_more_jobs:not(.load_previous)",h).hide():f||a(".load_more_jobs",h).show(),a(".load_more_jobs",h).removeClass("loading"),a("li.job_listing",k).css("visibility","visible")),a(k).removeClass("loading"),h.triggerHandler("updated_results",b)}catch(c){window.console&&console.log(c)}},error:function(a,b,c){window.console&&"abort"!==b&&console.log(b+": "+c)},statusCode:{404:function(){window.console&&console.log("Error 404: Ajax Endpoint cannot be reached. Go to Settings > Permalinks and save to resolve.")}}})}}),a("#search_keywords, #search_location, .job_types :input, #search_categories, .job-manager-filter").change(function(){var c=a(this).closest("div.job_listings");c.triggerHandler("update_results",[1,!1]),b(c,1)}).on("keyup",function(b){13===b.which&&a(this).trigger("change")}),a(".job_filters").on("click",".reset",function(){var c=a(this).closest("div.job_listings"),d=a(this).closest("form");return d.find(':input[name="search_keywords"], :input[name="search_location"], .job-manager-filter').not(':input[type="hidden"]').val("").trigger("chosen:updated"),d.find(':input[name^="search_categories"]').not(':input[type="hidden"]').val(0).trigger("chosen:updated"),a(':input[name="filter_job_type[]"]',d).not(':input[type="hidden"]').attr("checked","checked"),c.triggerHandler("reset"),c.triggerHandler("update_results",[1,!1]),b(c,1),!1}),a(document.body).on("click",".load_more_jobs",function(){var c=a(this).closest("div.job_listings"),d=parseInt(a(this).data("page")||1),e=!1;return a(this).addClass("loading"),a(this).is(".load_previous")?(d-=1,e=!0,1===d?a(this).remove():a(this).data("page",d)):(d+=1,a(this).data("page",d),b(c,d)),c.triggerHandler("update_results",[d,!0,e]),!1}),a("div.job_listings").on("click",".job-manager-pagination a",function(){var c=a(this).closest("div.job_listings"),d=a(this).data("page");return b(c,d),c.triggerHandler("update_results",[d,!1]),a("body, html").animate({scrollTop:c.offset().top},600),!1}),a.isFunction(a.fn.chosen)&&(1==job_manager_ajax_filters.is_rtl&&a('select[name^="search_categories"]').addClass("chosen-rtl"),a('select[name^="search_categories"]').chosen({search_contains:!0})),window.history&&window.history.pushState?$supports_html5_history=!0:$supports_html5_history=!1;var d=document.location.href.split("#")[0];a(window).on("load",function(b){a(".job_filters").each(function(){var b=a(this).closest("div.job_listings"),c=b.find(".job_filters"),d=1,e=a("div.job_listings").index(b);if(window.history.state&&window.location.hash){var f=window.history.state;f.id&&"job_manager_state"===f.id&&e==f.index&&(d=f.page,c.deserialize(f.data),c.find(':input[name^="search_categories"]').not(':input[type="hidden"]').trigger("chosen:updated"))}b.triggerHandler("update_results",[d,!1])})})});
assets/js/datepicker.min.js DELETED
@@ -1 +0,0 @@
1
- jQuery(document).ready(function(t){var e={altFormat:"yy-mm-dd"};"undefined"!=typeof job_manager_datepicker&&(e.dateFormat=job_manager_datepicker.date_format),t("input.job-manager-datepicker, input#_job_expires").each(function(){var a=t("<input />",{type:"hidden",name:t(this).attr("name")}).insertAfter(t(this));if(t(this).attr("name",t(this).attr("name")+"-datepicker"),t(this).keyup(function(){""===t(this).val()&&a.val("")}),t(this).datepicker(t.extend({},e,{altField:a})),t(this).val()){var i=t(this).val().split("-");if(3===i.length){var n=new Date(parseInt(i[0],10),parseInt(i[1],10)-1,parseInt(i[2],10));t(this).datepicker("setDate",n)}}})});
 
assets/js/job-application.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ jQuery(document).ready(function($) {
2
+ // Slide toggle
3
+ jQuery( '.application_details' ).hide();
4
+ jQuery( '.application_button' ).click(function() {
5
+ jQuery( '.application_details' ).slideToggle();
6
+ });
7
+ });
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(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)}})})});
1
+ jQuery(document).ready(function(a){jQuery(".application_details").hide(),jQuery(".application_button").click(function(){jQuery(".application_details").slideToggle()})});
assets/js/job-dashboard.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ jQuery(document).ready(function($) {
2
+
3
+ $('.job-dashboard-action-delete').click(function() {
4
+ return confirm( job_manager_job_dashboard.i18n_confirm_delete );
5
+ });
6
+
7
+ });
assets/js/job-dashboard.min.js CHANGED
@@ -1 +1 @@
1
- jQuery(document).ready(function(n){n(".job-dashboard-action-delete").click(function(){return window.confirm(job_manager_job_dashboard.i18n_confirm_delete)})});
1
+ jQuery(document).ready(function(a){a(".job-dashboard-action-delete").click(function(){return confirm(job_manager_job_dashboard.i18n_confirm_delete)})});
assets/js/job-submission.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ jQuery(document).ready(function($) {
2
+ jQuery('body').on( 'click', '.job-manager-remove-uploaded-file', function() {
3
+ jQuery(this).closest( '.job-manager-uploaded-file' ).remove();
4
+ return false;
5
+ });
6
+ });
assets/js/job-submission.min.js CHANGED
@@ -1 +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})})});
1
+ jQuery(document).ready(function(a){jQuery("body").on("click",".job-manager-remove-uploaded-file",function(){return jQuery(this).closest(".job-manager-uploaded-file").remove(),!1})});
assets/js/jquery-chosen/chosen.jquery.js ADDED
@@ -0,0 +1,1229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*!
2
+ Chosen, a Select Box Enhancer for jQuery and Prototype
3
+ by Patrick Filler for Harvest, http://getharvest.com
4
+
5
+ Version 1.2.0
6
+ Full source at https://github.com/harvesthq/chosen
7
+ Copyright (c) 2011-2014 Harvest http://getharvest.com
8
+
9
+ MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
10
+ This file is generated by `grunt build`, do not edit it by hand.
11
+ */
12
+
13
+ (function() {
14
+ var $, AbstractChosen, Chosen, SelectParser, _ref,
15
+ __hasProp = {}.hasOwnProperty,
16
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
17
+
18
+ SelectParser = (function() {
19
+ function SelectParser() {
20
+ this.options_index = 0;
21
+ this.parsed = [];
22
+ }
23
+
24
+ SelectParser.prototype.add_node = function(child) {
25
+ if (child.nodeName.toUpperCase() === "OPTGROUP") {
26
+ return this.add_group(child);
27
+ } else {
28
+ return this.add_option(child);
29
+ }
30
+ };
31
+
32
+ SelectParser.prototype.add_group = function(group) {
33
+ var group_position, option, _i, _len, _ref, _results;
34
+ group_position = this.parsed.length;
35
+ this.parsed.push({
36
+ array_index: group_position,
37
+ group: true,
38
+ label: this.escapeExpression(group.label),
39
+ children: 0,
40
+ disabled: group.disabled
41
+ });
42
+ _ref = group.childNodes;
43
+ _results = [];
44
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
45
+ option = _ref[_i];
46
+ _results.push(this.add_option(option, group_position, group.disabled));
47
+ }
48
+ return _results;
49
+ };
50
+
51
+ SelectParser.prototype.add_option = function(option, group_position, group_disabled) {
52
+ if (option.nodeName.toUpperCase() === "OPTION") {
53
+ if (option.text !== "") {
54
+ if (group_position != null) {
55
+ this.parsed[group_position].children += 1;
56
+ }
57
+ this.parsed.push({
58
+ array_index: this.parsed.length,
59
+ options_index: this.options_index,
60
+ value: option.value,
61
+ text: option.text,
62
+ html: option.innerHTML,
63
+ selected: option.selected,
64
+ disabled: group_disabled === true ? group_disabled : option.disabled,
65
+ group_array_index: group_position,
66
+ classes: option.className,
67
+ style: option.style.cssText
68
+ });
69
+ } else {
70
+ this.parsed.push({
71
+ array_index: this.parsed.length,
72
+ options_index: this.options_index,
73
+ empty: true
74
+ });
75
+ }
76
+ return this.options_index += 1;
77
+ }
78
+ };
79
+
80
+ SelectParser.prototype.escapeExpression = function(text) {
81
+ var map, unsafe_chars;
82
+ if ((text == null) || text === false) {
83
+ return "";
84
+ }
85
+ if (!/[\&\<\>\"\'\`]/.test(text)) {
86
+ return text;
87
+ }
88
+ map = {
89
+ "<": "&lt;",
90
+ ">": "&gt;",
91
+ '"': "&quot;",
92
+ "'": "&#x27;",
93
+ "`": "&#x60;"
94
+ };
95
+ unsafe_chars = /&(?!\w+;)|[\<\>\"\'\`]/g;
96
+ return text.replace(unsafe_chars, function(chr) {
97
+ return map[chr] || "&amp;";
98
+ });
99
+ };
100
+
101
+ return SelectParser;
102
+
103
+ })();
104
+
105
+ SelectParser.select_to_array = function(select) {
106
+ var child, parser, _i, _len, _ref;
107
+ parser = new SelectParser();
108
+ _ref = select.childNodes;
109
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
110
+ child = _ref[_i];
111
+ parser.add_node(child);
112
+ }
113
+ return parser.parsed;
114
+ };
115
+
116
+ AbstractChosen = (function() {
117
+ function AbstractChosen(form_field, options) {
118
+ this.form_field = form_field;
119
+ this.options = options != null ? options : {};
120
+ if (!AbstractChosen.browser_is_supported()) {
121
+ return;
122
+ }
123
+ this.is_multiple = this.form_field.multiple;
124
+ this.set_default_text();
125
+ this.set_default_values();
126
+ this.setup();
127
+ this.set_up_html();
128
+ this.register_observers();
129
+ }
130
+
131
+ AbstractChosen.prototype.set_default_values = function() {
132
+ var _this = this;
133
+ this.click_test_action = function(evt) {
134
+ return _this.test_active_click(evt);
135
+ };
136
+ this.activate_action = function(evt) {
137
+ return _this.activate_field(evt);
138
+ };
139
+ this.active_field = false;
140
+ this.mouse_on_container = false;
141
+ this.results_showing = false;
142
+ this.result_highlighted = null;
143
+ this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false;
144
+ this.disable_search_threshold = this.options.disable_search_threshold || 0;
145
+ this.disable_search = this.options.disable_search || false;
146
+ this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true;
147
+ this.group_search = this.options.group_search != null ? this.options.group_search : true;
148
+ this.search_contains = this.options.search_contains || false;
149
+ this.single_backstroke_delete = this.options.single_backstroke_delete != null ? this.options.single_backstroke_delete : true;
150
+ this.max_selected_options = this.options.max_selected_options || Infinity;
151
+ this.inherit_select_classes = this.options.inherit_select_classes || false;
152
+ this.display_selected_options = this.options.display_selected_options != null ? this.options.display_selected_options : true;
153
+ return this.display_disabled_options = this.options.display_disabled_options != null ? this.options.display_disabled_options : true;
154
+ };
155
+
156
+ AbstractChosen.prototype.set_default_text = function() {
157
+ if (this.form_field.getAttribute("data-placeholder")) {
158
+ this.default_text = this.form_field.getAttribute("data-placeholder");
159
+ } else if (this.is_multiple) {
160
+ this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text;
161
+ } else {
162
+ this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text;
163
+ }
164
+ return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || AbstractChosen.default_no_result_text;
165
+ };
166
+
167
+ AbstractChosen.prototype.mouse_enter = function() {
168
+ return this.mouse_on_container = true;
169
+ };
170
+
171
+ AbstractChosen.prototype.mouse_leave = function() {
172
+ return this.mouse_on_container = false;
173
+ };
174
+
175
+ AbstractChosen.prototype.input_focus = function(evt) {
176
+ var _this = this;
177
+ if (this.is_multiple) {
178
+ if (!this.active_field) {
179
+ return setTimeout((function() {
180
+ return _this.container_mousedown();
181
+ }), 50);
182
+ }
183
+ } else {
184
+ if (!this.active_field) {
185
+ return this.activate_field();
186
+ }
187
+ }
188
+ };
189
+
190
+ AbstractChosen.prototype.input_blur = function(evt) {
191
+ var _this = this;
192
+ if (!this.mouse_on_container) {
193
+ this.active_field = false;
194
+ return setTimeout((function() {
195
+ return _this.blur_test();
196
+ }), 100);
197
+ }
198
+ };
199
+
200
+ AbstractChosen.prototype.results_option_build = function(options) {
201
+ var content, data, _i, _len, _ref;
202
+ content = '';
203
+ _ref = this.results_data;
204
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
205
+ data = _ref[_i];
206
+ if (data.group) {
207
+ content += this.result_add_group(data);
208
+ } else {
209
+ content += this.result_add_option(data);
210
+ }
211
+ if (options != null ? options.first : void 0) {
212
+ if (data.selected && this.is_multiple) {
213
+ this.choice_build(data);
214
+ } else if (data.selected && !this.is_multiple) {
215
+ this.single_set_selected_text(data.text);
216
+ }
217
+ }
218
+ }
219
+ return content;
220
+ };
221
+
222
+ AbstractChosen.prototype.result_add_option = function(option) {
223
+ var classes, option_el;
224
+ if (!option.search_match) {
225
+ return '';
226
+ }
227
+ if (!this.include_option_in_results(option)) {
228
+ return '';
229
+ }
230
+ classes = [];
231
+ if (!option.disabled && !(option.selected && this.is_multiple)) {
232
+ classes.push("active-result");
233
+ }
234
+ if (option.disabled && !(option.selected && this.is_multiple)) {
235
+ classes.push("disabled-result");
236
+ }
237
+ if (option.selected) {
238
+ classes.push("result-selected");
239
+ }
240
+ if (option.group_array_index != null) {
241
+ classes.push("group-option");
242
+ }
243
+ if (option.classes !== "") {
244
+ classes.push(option.classes);
245
+ }
246
+ option_el = document.createElement("li");
247
+ option_el.className = classes.join(" ");
248
+ option_el.style.cssText = option.style;
249
+ option_el.setAttribute("data-option-array-index", option.array_index);
250
+ option_el.innerHTML = option.search_text;
251
+ return this.outerHTML(option_el);
252
+ };
253
+
254
+ AbstractChosen.prototype.result_add_group = function(group) {
255
+ var group_el;
256
+ if (!(group.search_match || group.group_match)) {
257
+ return '';
258
+ }
259
+ if (!(group.active_options > 0)) {
260
+ return '';
261
+ }
262
+ group_el = document.createElement("li");
263
+ group_el.className = "group-result";
264
+ group_el.innerHTML = group.search_text;
265
+ return this.outerHTML(group_el);
266
+ };
267
+
268
+ AbstractChosen.prototype.results_update_field = function() {
269
+ this.set_default_text();
270
+ if (!this.is_multiple) {
271
+ this.results_reset_cleanup();
272
+ }
273
+ this.result_clear_highlight();
274
+ this.results_build();
275
+ if (this.results_showing) {
276
+ return this.winnow_results();
277
+ }
278
+ };
279
+
280
+ AbstractChosen.prototype.reset_single_select_options = function() {
281
+ var result, _i, _len, _ref, _results;
282
+ _ref = this.results_data;
283
+ _results = [];
284
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
285
+ result = _ref[_i];
286
+ if (result.selected) {
287
+ _results.push(result.selected = false);
288
+ } else {
289
+ _results.push(void 0);
290
+ }
291
+ }
292
+ return _results;
293
+ };
294
+
295
+ AbstractChosen.prototype.results_toggle = function() {
296
+ if (this.results_showing) {
297
+ return this.results_hide();
298
+ } else {
299
+ return this.results_show();
300
+ }
301
+ };
302
+
303
+ AbstractChosen.prototype.results_search = function(evt) {
304
+ if (this.results_showing) {
305
+ return this.winnow_results();
306
+ } else {
307
+ return this.results_show();
308
+ }
309
+ };
310
+
311
+ AbstractChosen.prototype.winnow_results = function() {
312
+ var escapedSearchText, option, regex, results, results_group, searchText, startpos, text, zregex, _i, _len, _ref;
313
+ this.no_results_clear();
314
+ results = 0;
315
+ searchText = this.get_search_text();
316
+ escapedSearchText = searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
317
+ zregex = new RegExp(escapedSearchText, 'i');
318
+ regex = this.get_search_regex(escapedSearchText);
319
+ _ref = this.results_data;
320
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
321
+ option = _ref[_i];
322
+ option.search_match = false;
323
+ results_group = null;
324
+ if (this.include_option_in_results(option)) {
325
+ if (option.group) {
326
+ option.group_match = false;
327
+ option.active_options = 0;
328
+ }
329
+ if ((option.group_array_index != null) && this.results_data[option.group_array_index]) {
330
+ results_group = this.results_data[option.group_array_index];
331
+ if (results_group.active_options === 0 && results_group.search_match) {
332
+ results += 1;
333
+ }
334
+ results_group.active_options += 1;
335
+ }
336
+ if (!(option.group && !this.group_search)) {
337
+ option.search_text = option.group ? option.label : option.text;
338
+ option.search_match = this.search_string_match(option.search_text, regex);
339
+ if (option.search_match && !option.group) {
340
+ results += 1;
341
+ }
342
+ if (option.search_match) {
343
+ if (searchText.length) {
344
+ startpos = option.search_text.search(zregex);
345
+ text = option.search_text.substr(0, startpos + searchText.length) + '</em>' + option.search_text.substr(startpos + searchText.length);
346
+ option.search_text = text.substr(0, startpos) + '<em>' + text.substr(startpos);
347
+ }
348
+ if (results_group != null) {
349
+ results_group.group_match = true;
350
+ }
351
+ } else if ((option.group_array_index != null) && this.results_data[option.group_array_index].search_match) {
352
+ option.search_match = true;
353
+ }
354
+ }
355
+ }
356
+ }
357
+ this.result_clear_highlight();
358
+ if (results < 1 && searchText.length) {
359
+ this.update_results_content("");
360
+ return this.no_results(searchText);
361
+ } else {
362
+ this.update_results_content(this.results_option_build());
363
+ return this.winnow_results_set_highlight();
364
+ }
365
+ };
366
+
367
+ AbstractChosen.prototype.get_search_regex = function(escaped_search_string) {
368
+ var regex_anchor;
369
+ regex_anchor = this.search_contains ? "" : "^";
370
+ return new RegExp(regex_anchor + escaped_search_string, 'i');
371
+ };
372
+
373
+ AbstractChosen.prototype.search_string_match = function(search_string, regex) {
374
+ var part, parts, _i, _len;
375
+ if (regex.test(search_string)) {
376
+ return true;
377
+ } else if (this.enable_split_word_search && (search_string.indexOf(" ") >= 0 || search_string.indexOf("[") === 0)) {
378
+ parts = search_string.replace(/\[|\]/g, "").split(" ");
379
+ if (parts.length) {
380
+ for (_i = 0, _len = parts.length; _i < _len; _i++) {
381
+ part = parts[_i];
382
+ if (regex.test(part)) {
383
+ return true;
384
+ }
385
+ }
386
+ }
387
+ }
388
+ };
389
+
390
+ AbstractChosen.prototype.choices_count = function() {
391
+ var option, _i, _len, _ref;
392
+ if (this.selected_option_count != null) {
393
+ return this.selected_option_count;
394
+ }
395
+ this.selected_option_count = 0;
396
+ _ref = this.form_field.options;
397
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
398
+ option = _ref[_i];
399
+ if (option.selected) {
400
+ this.selected_option_count += 1;
401
+ }
402
+ }
403
+ return this.selected_option_count;
404
+ };
405
+
406
+ AbstractChosen.prototype.choices_click = function(evt) {
407
+ evt.preventDefault();
408
+ if (!(this.results_showing || this.is_disabled)) {
409
+ return this.results_show();
410
+ }
411
+ };
412
+
413
+ AbstractChosen.prototype.keyup_checker = function(evt) {
414
+ var stroke, _ref;
415
+ stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
416
+ this.search_field_scale();
417
+ switch (stroke) {
418
+ case 8:
419
+ if (this.is_multiple && this.backstroke_length < 1 && this.choices_count() > 0) {
420
+ return this.keydown_backstroke();
421
+ } else if (!this.pending_backstroke) {
422
+ this.result_clear_highlight();
423
+ return this.results_search();
424
+ }
425
+ break;
426
+ case 13:
427
+ evt.preventDefault();
428
+ if (this.results_showing) {
429
+ return this.result_select(evt);
430
+ }
431
+ break;
432
+ case 27:
433
+ if (this.results_showing) {
434
+ this.results_hide();
435
+ }
436
+ return true;
437
+ case 9:
438
+ case 38:
439
+ case 40:
440
+ case 16:
441
+ case 91:
442
+ case 17:
443
+ break;
444
+ default:
445
+ return this.results_search();
446
+ }
447
+ };
448
+
449
+ AbstractChosen.prototype.clipboard_event_checker = function(evt) {
450
+ var _this = this;
451
+ return setTimeout((function() {
452
+ return _this.results_search();
453
+ }), 50);
454
+ };
455
+
456
+ AbstractChosen.prototype.container_width = function() {
457
+ if (this.options.width != null) {
458
+ return this.options.width;
459
+ } else {
460
+ return "" + this.form_field.offsetWidth + "px";
461
+ }
462
+ };
463
+
464
+ AbstractChosen.prototype.include_option_in_results = function(option) {
465
+ if (this.is_multiple && (!this.display_selected_options && option.selected)) {
466
+ return false;
467
+ }
468
+ if (!this.display_disabled_options && option.disabled) {
469
+ return false;
470
+ }
471
+ if (option.empty) {
472
+ return false;
473
+ }
474
+ return true;
475
+ };
476
+
477
+ AbstractChosen.prototype.search_results_touchstart = function(evt) {
478
+ this.touch_started = true;
479
+ return this.search_results_mouseover(evt);
480
+ };
481
+
482
+ AbstractChosen.prototype.search_results_touchmove = function(evt) {
483
+ this.touch_started = false;
484
+ return this.search_results_mouseout(evt);
485
+ };
486
+
487
+ AbstractChosen.prototype.search_results_touchend = function(evt) {
488
+ if (this.touch_started) {
489
+ return this.search_results_mouseup(evt);
490
+ }
491
+ };
492
+
493
+ AbstractChosen.prototype.outerHTML = function(element) {
494
+ var tmp;
495
+ if (element.outerHTML) {
496
+ return element.outerHTML;
497
+ }
498
+ tmp = document.createElement("div");
499
+ tmp.appendChild(element);
500
+ return tmp.innerHTML;
501
+ };
502
+
503
+ AbstractChosen.browser_is_supported = function() {
504
+ if (window.navigator.appName === "Microsoft Internet Explorer") {
505
+ return document.documentMode >= 8;
506
+ }
507
+ if (/iP(od|hone)/i.test(window.navigator.userAgent)) {
508
+ return false;
509
+ }
510
+ if (/Android/i.test(window.navigator.userAgent)) {
511
+ if (/Mobile/i.test(window.navigator.userAgent)) {
512
+ return false;
513
+ }
514
+ }
515
+ return true;
516
+ };
517
+
518
+ AbstractChosen.default_multiple_text = "Select Some Options";
519
+
520
+ AbstractChosen.default_single_text = "Select an Option";
521
+
522
+ AbstractChosen.default_no_result_text = "No results match";
523
+
524
+ return AbstractChosen;
525
+
526
+ })();
527
+
528
+ $ = jQuery;
529
+
530
+ $.fn.extend({
531
+ chosen: function(options) {
532
+ if (!AbstractChosen.browser_is_supported()) {
533
+ return this;
534
+ }
535
+ return this.each(function(input_field) {
536
+ var $this, chosen;
537
+ $this = $(this);
538
+ chosen = $this.data('chosen');
539
+ if (options === 'destroy' && chosen instanceof Chosen) {
540
+ chosen.destroy();
541
+ } else if (!(chosen instanceof Chosen)) {
542
+ $this.data('chosen', new Chosen(this, options));
543
+ }
544
+ });
545
+ }
546
+ });
547
+
548
+ Chosen = (function(_super) {
549
+ __extends(Chosen, _super);
550
+
551
+ function Chosen() {
552
+ _ref = Chosen.__super__.constructor.apply(this, arguments);
553
+ return _ref;
554
+ }
555
+
556
+ Chosen.prototype.setup = function() {
557
+ this.form_field_jq = $(this.form_field);
558
+ this.current_selectedIndex = this.form_field.selectedIndex;
559
+ return this.is_rtl = this.form_field_jq.hasClass("chosen-rtl");
560
+ };
561
+
562
+ Chosen.prototype.set_up_html = function() {
563
+ var container_classes, container_props;
564
+ container_classes = ["chosen-container"];
565
+ container_classes.push("chosen-container-" + (this.is_multiple ? "multi" : "single"));
566
+ if (this.inherit_select_classes && this.form_field.className) {
567
+ container_classes.push(this.form_field.className);
568
+ }
569
+ if (this.is_rtl) {
570
+ container_classes.push("chosen-rtl");
571
+ }
572
+ container_props = {
573
+ 'class': container_classes.join(' '),
574
+ 'style': "width: " + (this.container_width()) + ";",
575
+ 'title': this.form_field.title
576
+ };
577
+ if (this.form_field.id.length) {
578
+ container_props.id = this.form_field.id.replace(/[^\w]/g, '_') + "_chosen";
579
+ }
580
+ this.container = $("<div />", container_props);
581
+ if (this.is_multiple) {
582
+ this.container.html('<ul class="chosen-choices"><li class="search-field"><input type="text" value="' + this.default_text + '" class="default" autocomplete="off" style="width:25px;" /></li></ul><div class="chosen-drop"><ul class="chosen-results"></ul></div>');
583
+ } else {
584
+ this.container.html('<a class="chosen-single chosen-default" tabindex="-1"><span>' + this.default_text + '</span><div><b></b></div></a><div class="chosen-drop"><div class="chosen-search"><input type="text" autocomplete="off" /></div><ul class="chosen-results"></ul></div>');
585
+ }
586
+ this.form_field_jq.hide().after(this.container);
587
+ this.dropdown = this.container.find('div.chosen-drop').first();
588
+ this.search_field = this.container.find('input').first();
589
+ this.search_results = this.container.find('ul.chosen-results').first();
590
+ this.search_field_scale();
591
+ this.search_no_results = this.container.find('li.no-results').first();
592
+ if (this.is_multiple) {
593
+ this.search_choices = this.container.find('ul.chosen-choices').first();
594
+ this.search_container = this.container.find('li.search-field').first();
595
+ } else {
596
+ this.search_container = this.container.find('div.chosen-search').first();
597
+ this.selected_item = this.container.find('.chosen-single').first();
598
+ }
599
+ this.results_build();
600
+ this.set_tab_index();
601
+ this.set_label_behavior();
602
+ return this.form_field_jq.trigger("chosen:ready", {
603
+ chosen: this
604
+ });
605
+ };
606
+
607
+ Chosen.prototype.register_observers = function() {
608
+ var _this = this;
609
+ this.container.bind('touchstart.chosen', function(evt) {
610
+ _this.container_mousedown(evt);
611
+ });
612
+ this.container.bind('touchend.chosen', function(evt) {
613
+ _this.container_mouseup(evt);
614
+ });
615
+ this.container.bind('mousedown.chosen', function(evt) {
616
+ _this.container_mousedown(evt);
617
+ });
618
+ this.container.bind('mouseup.chosen', function(evt) {
619
+ _this.container_mouseup(evt);
620
+ });
621
+ this.container.bind('mouseenter.chosen', function(evt) {
622
+ _this.mouse_enter(evt);
623
+ });
624
+ this.container.bind('mouseleave.chosen', function(evt) {
625
+ _this.mouse_leave(evt);
626
+ });
627
+ this.search_results.bind('mouseup.chosen', function(evt) {
628
+ _this.search_results_mouseup(evt);
629
+ });
630
+ this.search_results.bind('mouseover.chosen', function(evt) {
631
+ _this.search_results_mouseover(evt);
632
+ });
633
+ this.search_results.bind('mouseout.chosen', function(evt) {
634
+ _this.search_results_mouseout(evt);
635
+ });
636
+ this.search_results.bind('mousewheel.chosen DOMMouseScroll.chosen', function(evt) {
637
+ _this.search_results_mousewheel(evt);
638
+ });
639
+ this.search_results.bind('touchstart.chosen', function(evt) {
640
+ _this.search_results_touchstart(evt);
641
+ });
642
+ this.search_results.bind('touchmove.chosen', function(evt) {
643
+ _this.search_results_touchmove(evt);
644
+ });
645
+ this.search_results.bind('touchend.chosen', function(evt) {
646
+ _this.search_results_touchend(evt);
647
+ });
648
+ this.form_field_jq.bind("chosen:updated.chosen", function(evt) {
649
+ _this.results_update_field(evt);
650
+ });
651
+ this.form_field_jq.bind("chosen:activate.chosen", function(evt) {
652
+ _this.activate_field(evt);
653
+ });
654
+ this.form_field_jq.bind("chosen:open.chosen", function(evt) {
655
+ _this.container_mousedown(evt);
656
+ });
657
+ this.form_field_jq.bind("chosen:close.chosen", function(evt) {
658
+ _this.input_blur(evt);
659
+ });
660
+ this.search_field.bind('blur.chosen', function(evt) {
661
+ _this.input_blur(evt);
662
+ });
663
+ this.search_field.bind('keyup.chosen', function(evt) {
664
+ _this.keyup_checker(evt);
665
+ });
666
+ this.search_field.bind('keydown.chosen', function(evt) {
667
+ _this.keydown_checker(evt);
668
+ });
669
+ this.search_field.bind('focus.chosen', function(evt) {
670
+ _this.input_focus(evt);
671
+ });
672
+ this.search_field.bind('cut.chosen', function(evt) {
673
+ _this.clipboard_event_checker(evt);
674
+ });
675
+ this.search_field.bind('paste.chosen', function(evt) {
676
+ _this.clipboard_event_checker(evt);
677
+ });
678
+ if (this.is_multiple) {
679
+ return this.search_choices.bind('click.chosen', function(evt) {
680
+ _this.choices_click(evt);
681
+ });
682
+ } else {
683
+ return this.container.bind('click.chosen', function(evt) {
684
+ evt.preventDefault();
685
+ });
686
+ }
687
+ };
688
+
689
+ Chosen.prototype.destroy = function() {
690
+ $(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
691
+ if (this.search_field[0].tabIndex) {
692
+ this.form_field_jq[0].tabIndex = this.search_field[0].tabIndex;
693
+ }
694
+ this.container.remove();
695
+ this.form_field_jq.removeData('chosen');
696
+ return this.form_field_jq.show();
697
+ };
698
+
699
+ Chosen.prototype.search_field_disabled = function() {
700
+ this.is_disabled = this.form_field_jq[0].disabled;
701
+ if (this.is_disabled) {
702
+ this.container.addClass('chosen-disabled');
703
+ this.search_field[0].disabled = true;
704
+ if (!this.is_multiple) {
705
+ this.selected_item.unbind("focus.chosen", this.activate_action);
706
+ }
707
+ return this.close_field();
708
+ } else {
709
+ this.container.removeClass('chosen-disabled');
710
+ this.search_field[0].disabled = false;
711
+ if (!this.is_multiple) {
712
+ return this.selected_item.bind("focus.chosen", this.activate_action);
713
+ }
714
+ }
715
+ };
716
+
717
+ Chosen.prototype.container_mousedown = function(evt) {
718
+ if (!this.is_disabled) {
719
+ if (evt && evt.type === "mousedown" && !this.results_showing) {
720
+ evt.preventDefault();
721
+ }
722
+ if (!((evt != null) && ($(evt.target)).hasClass("search-choice-close"))) {
723
+ if (!this.active_field) {
724
+ if (this.is_multiple) {
725
+ this.search_field.val("");
726
+ }
727
+ $(this.container[0].ownerDocument).bind('click.chosen', this.click_test_action);
728
+ this.results_show();
729
+ } else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chosen-single").length)) {
730
+ evt.preventDefault();
731
+ this.results_toggle();
732
+ }
733
+ return this.activate_field();
734
+ }
735
+ }
736
+ };
737
+
738
+ Chosen.prototype.container_mouseup = function(evt) {
739
+ if (evt.target.nodeName === "ABBR" && !this.is_disabled) {
740
+ return this.results_reset(evt);
741
+ }
742
+ };
743
+
744
+ Chosen.prototype.search_results_mousewheel = function(evt) {
745
+ var delta;
746
+ if (evt.originalEvent) {
747
+ delta = evt.originalEvent.deltaY || -evt.originalEvent.wheelDelta || evt.originalEvent.detail;
748
+ }
749
+ if (delta != null) {
750
+ evt.preventDefault();
751
+ if (evt.type === 'DOMMouseScroll') {
752
+ delta = delta * 40;
753
+ }
754
+ return this.search_results.scrollTop(delta + this.search_results.scrollTop());
755
+ }
756
+ };
757
+
758
+ Chosen.prototype.blur_test = function(evt) {
759
+ if (!this.active_field && this.container.hasClass("chosen-container-active")) {
760
+ return this.close_field();
761
+ }
762
+ };
763
+
764
+ Chosen.prototype.close_field = function() {
765
+ $(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
766
+ this.active_field = false;
767
+ this.results_hide();
768
+ this.container.removeClass("chosen-container-active");
769
+ this.clear_backstroke();
770
+ this.show_search_field_default();
771
+ return this.search_field_scale();
772
+ };
773
+
774
+ Chosen.prototype.activate_field = function() {
775
+ this.container.addClass("chosen-container-active");
776
+ this.active_field = true;
777
+ this.search_field.val(this.search_field.val());
778
+ return this.search_field.focus();
779
+ };
780
+
781
+ Chosen.prototype.test_active_click = function(evt) {
782
+ var active_container;
783
+ active_container = $(evt.target).closest('.chosen-container');
784
+ if (active_container.length && this.container[0] === active_container[0]) {
785
+ return this.active_field = true;
786
+ } else {
787
+ return this.close_field();
788
+ }
789
+ };
790
+
791
+ Chosen.prototype.results_build = function() {
792
+ this.parsing = true;
793
+ this.selected_option_count = null;
794
+ this.results_data = SelectParser.select_to_array(this.form_field);
795
+ if (this.is_multiple) {
796
+ this.search_choices.find("li.search-choice").remove();
797
+ } else if (!this.is_multiple) {
798
+ this.single_set_selected_text();
799
+ if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) {
800
+ this.search_field[0].readOnly = true;
801
+ this.container.addClass("chosen-container-single-nosearch");
802
+ } else {
803
+ this.search_field[0].readOnly = false;
804
+ this.container.removeClass("chosen-container-single-nosearch");
805
+ }
806
+ }
807
+ this.update_results_content(this.results_option_build({
808
+ first: true
809
+ }));
810
+ this.search_field_disabled();
811
+ this.show_search_field_default();
812
+ this.search_field_scale();
813
+ return this.parsing = false;
814
+ };
815
+
816
+ Chosen.prototype.result_do_highlight = function(el) {
817
+ var high_bottom, high_top, maxHeight, visible_bottom, visible_top;
818
+ if (el.length) {
819
+ this.result_clear_highlight();
820
+ this.result_highlight = el;
821
+ this.result_highlight.addClass("highlighted");
822
+ maxHeight = parseInt(this.search_results.css("maxHeight"), 10);
823
+ visible_top = this.search_results.scrollTop();
824
+ visible_bottom = maxHeight + visible_top;
825
+ high_top = this.result_highlight.position().top + this.search_results.scrollTop();
826
+ high_bottom = high_top + this.result_highlight.outerHeight();
827
+ if (high_bottom >= visible_bottom) {
828
+ return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0);
829
+ } else if (high_top < visible_top) {
830
+ return this.search_results.scrollTop(high_top);
831
+ }
832
+ }
833
+ };
834
+
835
+ Chosen.prototype.result_clear_highlight = function() {
836
+ if (this.result_highlight) {
837
+ this.result_highlight.removeClass("highlighted");
838
+ }
839
+ return this.result_highlight = null;
840
+ };
841
+
842
+ Chosen.prototype.results_show = function() {
843
+ if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
844
+ this.form_field_jq.trigger("chosen:maxselected", {
845
+ chosen: this
846
+ });
847
+ return false;
848
+ }
849
+ this.container.addClass("chosen-with-drop");
850
+ this.results_showing = true;
851
+ this.search_field.focus();
852
+ this.search_field.val(this.search_field.val());
853
+ this.winnow_results();
854
+ return this.form_field_jq.trigger("chosen:showing_dropdown", {
855
+ chosen: this
856
+ });
857
+ };
858
+
859
+ Chosen.prototype.update_results_content = function(content) {
860
+ return this.search_results.html(content);
861
+ };
862
+
863
+ Chosen.prototype.results_hide = function() {
864
+ if (this.results_showing) {
865
+ this.result_clear_highlight();
866
+ this.container.removeClass("chosen-with-drop");
867
+ this.form_field_jq.trigger("chosen:hiding_dropdown", {
868
+ chosen: this
869
+ });
870
+ }
871
+ return this.results_showing = false;
872
+ };
873
+
874
+ Chosen.prototype.set_tab_index = function(el) {
875
+ var ti;
876
+ if (this.form_field.tabIndex) {
877
+ ti = this.form_field.tabIndex;
878
+ this.form_field.tabIndex = -1;
879
+ return this.search_field[0].tabIndex = ti;
880
+ }
881
+ };
882
+
883
+ Chosen.prototype.set_label_behavior = function() {
884
+ var _this = this;
885
+ this.form_field_label = this.form_field_jq.parents("label");
886
+ if (!this.form_field_label.length && this.form_field.id.length) {
887
+ this.form_field_label = $("label[for='" + this.form_field.id + "']");
888
+ }
889
+ if (this.form_field_label.length > 0) {
890
+ return this.form_field_label.bind('click.chosen', function(evt) {
891
+ if (_this.is_multiple) {
892
+ return _this.container_mousedown(evt);
893
+ } else {
894
+ return _this.activate_field();
895
+ }
896
+ });
897
+ }
898
+ };
899
+
900
+ Chosen.prototype.show_search_field_default = function() {
901
+ if (this.is_multiple && this.choices_count() < 1 && !this.active_field) {
902
+ this.search_field.val(this.default_text);
903
+ return this.search_field.addClass("default");
904
+ } else {
905
+ this.search_field.val("");
906
+ return this.search_field.removeClass("default");
907
+ }
908
+ };
909
+
910
+ Chosen.prototype.search_results_mouseup = function(evt) {
911
+ var target;
912
+ target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
913
+ if (target.length) {
914
+ this.result_highlight = target;
915
+ this.result_select(evt);
916
+ return this.search_field.focus();
917
+ }
918
+ };
919
+
920
+ Chosen.prototype.search_results_mouseover = function(evt) {
921
+ var target;
922
+ target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
923
+ if (target) {
924
+ return this.result_do_highlight(target);
925
+ }
926
+ };
927
+
928
+ Chosen.prototype.search_results_mouseout = function(evt) {
929
+ if ($(evt.target).hasClass("active-result" || $(evt.target).parents('.active-result').first())) {
930
+ return this.result_clear_highlight();
931
+ }
932
+ };
933
+
934
+ Chosen.prototype.choice_build = function(item) {
935
+ var choice, close_link,
936
+ _this = this;
937
+ choice = $('<li />', {
938
+ "class": "search-choice"
939
+ }).html("<span>" + item.html + "</span>");
940
+ if (item.disabled) {
941
+ choice.addClass('search-choice-disabled');
942
+ } else {
943
+ close_link = $('<a />', {
944
+ "class": 'search-choice-close',
945
+ 'data-option-array-index': item.array_index
946
+ });
947
+ close_link.bind('click.chosen', function(evt) {
948
+ return _this.choice_destroy_link_click(evt);
949
+ });
950
+ choice.append(close_link);
951
+ }
952
+ return this.search_container.before(choice);
953
+ };
954
+
955
+ Chosen.prototype.choice_destroy_link_click = function(evt) {
956
+ evt.preventDefault();
957
+ evt.stopPropagation();
958
+ if (!this.is_disabled) {
959
+ return this.choice_destroy($(evt.target));
960
+ }
961
+ };
962
+
963
+ Chosen.prototype.choice_destroy = function(link) {
964
+ if (this.result_deselect(link[0].getAttribute("data-option-array-index"))) {
965
+ this.show_search_field_default();
966
+ if (this.is_multiple && this.choices_count() > 0 && this.search_field.val().length < 1) {
967
+ this.results_hide();
968
+ }
969
+ link.parents('li').first().remove();
970
+ return this.search_field_scale();
971
+ }
972
+ };
973
+
974
+ Chosen.prototype.results_reset = function() {
975
+ this.reset_single_select_options();
976
+ this.form_field.options[0].selected = true;
977
+ this.single_set_selected_text();
978
+ this.show_search_field_default();
979
+ this.results_reset_cleanup();
980
+ this.form_field_jq.trigger("change");
981
+ if (this.active_field) {
982
+ return this.results_hide();
983
+ }
984
+ };
985
+
986
+ Chosen.prototype.results_reset_cleanup = function() {
987
+ this.current_selectedIndex = this.form_field.selectedIndex;
988
+ return this.selected_item.find("abbr").remove();
989
+ };
990
+
991
+ Chosen.prototype.result_select = function(evt) {
992
+ var high, item;
993
+ if (this.result_highlight) {
994
+ high = this.result_highlight;
995
+ this.result_clear_highlight();
996
+ if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
997
+ this.form_field_jq.trigger("chosen:maxselected", {
998
+ chosen: this
999
+ });
1000
+ return false;
1001
+ }
1002
+ if (this.is_multiple) {
1003
+ high.removeClass("active-result");
1004
+ } else {
1005
+ this.reset_single_select_options();
1006
+ }
1007
+ item = this.results_data[high[0].getAttribute("data-option-array-index")];
1008
+ item.selected = true;
1009
+ this.form_field.options[item.options_index].selected = true;
1010
+ this.selected_option_count = null;
1011
+ if (this.is_multiple) {
1012
+ this.choice_build(item);
1013
+ } else {
1014
+ this.single_set_selected_text(item.text);
1015
+ }
1016
+ if (!((evt.metaKey || evt.ctrlKey) && this.is_multiple)) {
1017
+ this.results_hide();
1018
+ }
1019
+ this.search_field.val("");
1020
+ if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex) {
1021
+ this.form_field_jq.trigger("change", {
1022
+ 'selected': this.form_field.options[item.options_index].value
1023
+ });
1024
+ }
1025
+ this.current_selectedIndex = this.form_field.selectedIndex;
1026
+ return this.search_field_scale();
1027
+ }
1028
+ };
1029
+
1030
+ Chosen.prototype.single_set_selected_text = function(text) {
1031
+ if (text == null) {
1032
+ text = this.default_text;
1033
+ }
1034
+ if (text === this.default_text) {
1035
+ this.selected_item.addClass("chosen-default");
1036
+ } else {
1037
+ this.single_deselect_control_build();
1038
+ this.selected_item.removeClass("chosen-default");
1039
+ }
1040
+ return this.selected_item.find("span").text(text);
1041
+ };
1042
+
1043
+ Chosen.prototype.result_deselect = function(pos) {
1044
+ var result_data;
1045
+ result_data = this.results_data[pos];
1046
+ if (!this.form_field.options[result_data.options_index].disabled) {
1047
+ result_data.selected = false;
1048
+ this.form_field.options[result_data.options_index].selected = false;
1049
+ this.selected_option_count = null;
1050
+ this.result_clear_highlight();
1051
+ if (this.results_showing) {
1052
+ this.winnow_results();
1053
+ }
1054
+ this.form_field_jq.trigger("change", {
1055
+ deselected: this.form_field.options[result_data.options_index].value
1056
+ });
1057
+ this.search_field_scale();
1058
+ return true;
1059
+ } else {
1060
+ return false;
1061
+ }
1062
+ };
1063
+
1064
+ Chosen.prototype.single_deselect_control_build = function() {
1065
+ if (!this.allow_single_deselect) {
1066
+ return;
1067
+ }
1068
+ if (!this.selected_item.find("abbr").length) {
1069
+ this.selected_item.find("span").first().after("<abbr class=\"search-choice-close\"></abbr>");
1070
+ }
1071
+ return this.selected_item.addClass("chosen-single-with-deselect");
1072
+ };
1073
+
1074
+ Chosen.prototype.get_search_text = function() {
1075
+ if (this.search_field.val() === this.default_text) {
1076
+ return "";
1077
+ } else {
1078
+ return $('<div/>').text($.trim(this.search_field.val())).html();
1079
+ }
1080
+ };
1081
+
1082
+ Chosen.prototype.winnow_results_set_highlight = function() {
1083
+ var do_high, selected_results;
1084
+ selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : [];
1085
+ do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first();
1086
+ if (do_high != null) {
1087
+ return this.result_do_highlight(do_high);
1088
+ }
1089
+ };
1090
+
1091
+ Chosen.prototype.no_results = function(terms) {
1092
+ var no_results_html;
1093
+ no_results_html = $('<li class="no-results">' + this.results_none_found + ' "<span></span>"</li>');
1094
+ no_results_html.find("span").first().html(terms);
1095
+ this.search_results.append(no_results_html);
1096
+ return this.form_field_jq.trigger("chosen:no_results", {
1097
+ chosen: this
1098
+ });
1099
+ };
1100
+
1101
+ Chosen.prototype.no_results_clear = function() {
1102
+ return this.search_results.find(".no-results").remove();
1103
+ };
1104
+
1105
+ Chosen.prototype.keydown_arrow = function() {
1106
+ var next_sib;
1107
+ if (this.results_showing && this.result_highlight) {
1108
+ next_sib = this.result_highlight.nextAll("li.active-result").first();
1109
+ if (next_sib) {
1110
+ return this.result_do_highlight(next_sib);
1111
+ }
1112
+ } else {
1113
+ return this.results_show();
1114
+ }
1115
+ };
1116
+
1117
+ Chosen.prototype.keyup_arrow = function() {
1118
+ var prev_sibs;
1119
+ if (!this.results_showing && !this.is_multiple) {
1120
+ return this.results_show();
1121
+ } else if (this.result_highlight) {
1122
+ prev_sibs = this.result_highlight.prevAll("li.active-result");
1123
+ if (prev_sibs.length) {
1124
+ return this.result_do_highlight(prev_sibs.first());
1125
+ } else {
1126
+ if (this.choices_count() > 0) {
1127
+ this.results_hide();
1128
+ }
1129
+ return this.result_clear_highlight();
1130
+ }
1131
+ }
1132
+ };
1133
+
1134
+ Chosen.prototype.keydown_backstroke = function() {
1135
+ var next_available_destroy;
1136
+ if (this.pending_backstroke) {
1137
+ this.choice_destroy(this.pending_backstroke.find("a").first());
1138
+ return this.clear_backstroke();
1139
+ } else {
1140
+ next_available_destroy = this.search_container.siblings("li.search-choice").last();
1141
+ if (next_available_destroy.length && !next_available_destroy.hasClass("search-choice-disabled")) {
1142
+ this.pending_backstroke = next_available_destroy;
1143
+ if (this.single_backstroke_delete) {
1144
+ return this.keydown_backstroke();
1145
+ } else {
1146
+ return this.pending_backstroke.addClass("search-choice-focus");
1147
+ }
1148
+ }
1149
+ }
1150
+ };
1151
+
1152
+ Chosen.prototype.clear_backstroke = function() {
1153
+ if (this.pending_backstroke) {
1154
+ this.pending_backstroke.removeClass("search-choice-focus");
1155
+ }
1156
+ return this.pending_backstroke = null;
1157
+ };
1158
+
1159
+ Chosen.prototype.keydown_checker = function(evt) {
1160
+ var stroke, _ref1;
1161
+ stroke = (_ref1 = evt.which) != null ? _ref1 : evt.keyCode;
1162
+ this.search_field_scale();
1163
+ if (stroke !== 8 && this.pending_backstroke) {
1164
+ this.clear_backstroke();
1165
+ }
1166
+ switch (stroke) {
1167
+ case 8:
1168
+ this.backstroke_length = this.search_field.val().length;
1169
+ break;
1170
+ case 9:
1171
+ if (this.results_showing && !this.is_multiple) {
1172
+ this.result_select(evt);
1173
+ }
1174
+ this.mouse_on_container = false;
1175
+ break;
1176
+ case 13:
1177
+ if (this.results_showing) {
1178
+ evt.preventDefault();
1179
+ }
1180
+ break;
1181
+ case 32:
1182
+ if (this.disable_search) {
1183
+ evt.preventDefault();
1184
+ }
1185
+ break;
1186
+ case 38:
1187
+ evt.preventDefault();
1188
+ this.keyup_arrow();
1189
+ break;
1190
+ case 40:
1191
+ evt.preventDefault();
1192
+ this.keydown_arrow();
1193
+ break;
1194
+ }
1195
+ };
1196
+
1197
+ Chosen.prototype.search_field_scale = function() {
1198
+ var div, f_width, h, style, style_block, styles, w, _i, _len;
1199
+ if (this.is_multiple) {
1200
+ h = 0;
1201
+ w = 0;
1202
+ style_block = "position:absolute; left: -1000px; top: -1000px; display:none;";
1203
+ styles = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing'];
1204
+ for (_i = 0, _len = styles.length; _i < _len; _i++) {
1205
+ style = styles[_i];
1206
+ style_block += style + ":" + this.search_field.css(style) + ";";
1207
+ }
1208
+ div = $('<div />', {
1209
+ 'style': style_block
1210
+ });
1211
+ div.text(this.search_field.val());
1212
+ $('body').append(div);
1213
+ w = div.width() + 25;
1214
+ div.remove();
1215
+ f_width = this.container.outerWidth();
1216
+ if (w > f_width - 10) {
1217
+ w = f_width - 10;
1218
+ }
1219
+ return this.search_field.css({
1220
+ 'width': w + 'px'
1221
+ });
1222
+ }
1223
+ };
1224
+
1225
+ return Chosen;
1226
+
1227
+ })(AbstractChosen);
1228
+
1229
+ }).call(this);
assets/js/jquery-tiptip/jquery.tipTip.js ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ * TipTip
3
+ * Copyright 2010 Drew Wilson
4
+ * www.drewwilson.com
5
+ * code.drewwilson.com/entry/tiptip-jquery-plugin
6
+ *
7
+ * Version 1.3 - Updated: Mar. 23, 2010
8
+ *
9
+ * This Plug-In will create a custom tooltip to replace the default
10
+ * browser tooltip. It is extremely lightweight and very smart in
11
+ * that it detects the edges of the browser window and will make sure
12
+ * the tooltip stays within the current window size. As a result the
13
+ * tooltip will adjust itself to be displayed above, below, to the left
14
+ * or to the right depending on what is necessary to stay within the
15
+ * browser window. It is completely customizable as well via CSS.
16
+ *
17
+ * This TipTip jQuery plug-in is dual licensed under the MIT and GPL licenses:
18
+ * http://www.opensource.org/licenses/mit-license.php
19
+ * http://www.gnu.org/licenses/gpl.html
20
+ */
21
+
22
+ (function($){
23
+ $.fn.tipTip = function(options) {
24
+ var defaults = {
25
+ activation: "hover",
26
+ keepAlive: false,
27
+ maxWidth: "200px",
28
+ edgeOffset: 3,
29
+ defaultPosition: "bottom",
30
+ delay: 400,
31
+ fadeIn: 200,
32
+ fadeOut: 200,
33
+ attribute: "title",
34
+ content: false, // HTML or String to fill TipTIp with
35
+ enter: function(){},
36
+ exit: function(){}
37
+ };
38
+ var opts = $.extend(defaults, options);
39
+
40
+ // Setup tip tip elements and render them to the DOM
41
+ if($("#tiptip_holder").length <= 0){
42
+ var tiptip_holder = $('<div id="tiptip_holder" style="max-width:'+ opts.maxWidth +';"></div>');
43
+ var tiptip_content = $('<div id="tiptip_content"></div>');
44
+ var tiptip_arrow = $('<div id="tiptip_arrow"></div>');
45
+ $("body").append(tiptip_holder.html(tiptip_content).prepend(tiptip_arrow.html('<div id="tiptip_arrow_inner"></div>')));
46
+ } else {
47
+ var tiptip_holder = $("#tiptip_holder");
48
+ var tiptip_content = $("#tiptip_content");
49
+ var tiptip_arrow = $("#tiptip_arrow");
50
+ }
51
+
52
+ return this.each(function(){
53
+ var org_elem = $(this);
54
+ if(opts.content){
55
+ var org_title = opts.content;
56
+ } else {
57
+ var org_title = org_elem.attr(opts.attribute);
58
+ }
59
+ if(org_title != ""){
60
+ if(!opts.content){
61
+ org_elem.removeAttr(opts.attribute); //remove original Attribute
62
+ }
63
+ var timeout = false;
64
+
65
+ if(opts.activation == "hover"){
66
+ org_elem.hover(function(){
67
+ active_tiptip();
68
+ }, function(){
69
+ if(!opts.keepAlive){
70
+ deactive_tiptip();
71
+ }
72
+ });
73
+ if(opts.keepAlive){
74
+ tiptip_holder.hover(function(){}, function(){
75
+ deactive_tiptip();
76
+ });
77
+ }
78
+ } else if(opts.activation == "focus"){
79
+ org_elem.focus(function(){
80
+ active_tiptip();
81
+ }).blur(function(){
82
+ deactive_tiptip();
83
+ });
84
+ } else if(opts.activation == "click"){
85
+ org_elem.click(function(){
86
+ active_tiptip();
87
+ return false;
88
+ }).hover(function(){},function(){
89
+ if(!opts.keepAlive){
90
+ deactive_tiptip();
91
+ }
92
+ });
93
+ if(opts.keepAlive){
94
+ tiptip_holder.hover(function(){}, function(){
95
+ deactive_tiptip();
96
+ });
97
+ }
98
+ }
99
+
100
+ function active_tiptip(){
101
+ opts.enter.call(this);
102
+ tiptip_content.html(org_title);
103
+ tiptip_holder.hide().removeAttr("class").css("margin","0");
104
+ tiptip_arrow.removeAttr("style");
105
+
106
+ var top = parseInt(org_elem.offset()['top']);
107
+ var left = parseInt(org_elem.offset()['left']);
108
+ var org_width = parseInt(org_elem.outerWidth());
109
+ var org_height = parseInt(org_elem.outerHeight());
110
+ var tip_w = tiptip_holder.outerWidth();
111
+ var tip_h = tiptip_holder.outerHeight();
112
+ var w_compare = Math.round((org_width - tip_w) / 2);
113
+ var h_compare = Math.round((org_height - tip_h) / 2);
114
+ var marg_left = Math.round(left + w_compare);
115
+ var marg_top = Math.round(top + org_height + opts.edgeOffset);
116
+ var t_class = "";
117
+ var arrow_top = "";
118
+ var arrow_left = Math.round(tip_w - 12) / 2;
119
+
120
+ if(opts.defaultPosition == "bottom"){
121
+ t_class = "_bottom";
122
+ } else if(opts.defaultPosition == "top"){
123
+ t_class = "_top";
124
+ } else if(opts.defaultPosition == "left"){
125
+ t_class = "_left";
126
+ } else if(opts.defaultPosition == "right"){
127
+ t_class = "_right";
128
+ }
129
+
130
+ var right_compare = (w_compare + left) < parseInt($(window).scrollLeft());
131
+ var left_compare = (tip_w + left) > parseInt($(window).width());
132
+
133
+ if((right_compare && w_compare < 0) || (t_class == "_right" && !left_compare) || (t_class == "_left" && left < (tip_w + opts.edgeOffset + 5))){
134
+ t_class = "_right";
135
+ arrow_top = Math.round(tip_h - 13) / 2;
136
+ arrow_left = -12;
137
+ marg_left = Math.round(left + org_width + opts.edgeOffset);
138
+ marg_top = Math.round(top + h_compare);
139
+ } else if((left_compare && w_compare < 0) || (t_class == "_left" && !right_compare)){
140
+ t_class = "_left";
141
+ arrow_top = Math.round(tip_h - 13) / 2;
142
+ arrow_left = Math.round(tip_w);
143
+ marg_left = Math.round(left - (tip_w + opts.edgeOffset + 5));
144
+ marg_top = Math.round(top + h_compare);
145
+ }
146
+
147
+ var top_compare = (top + org_height + opts.edgeOffset + tip_h + 8) > parseInt($(window).height() + $(window).scrollTop());
148
+ var bottom_compare = ((top + org_height) - (opts.edgeOffset + tip_h + 8)) < 0;
149
+
150
+ if(top_compare || (t_class == "_bottom" && top_compare) || (t_class == "_top" && !bottom_compare)){
151
+ if(t_class == "_top" || t_class == "_bottom"){
152
+ t_class = "_top";
153
+ } else {
154
+ t_class = t_class+"_top";
155
+ }
156
+ arrow_top = tip_h;
157
+ marg_top = Math.round(top - (tip_h + 5 + opts.edgeOffset));
158
+ } else if(bottom_compare | (t_class == "_top" && bottom_compare) || (t_class == "_bottom" && !top_compare)){
159
+ if(t_class == "_top" || t_class == "_bottom"){
160
+ t_class = "_bottom";
161
+ } else {
162
+ t_class = t_class+"_bottom";
163
+ }
164
+ arrow_top = -12;
165
+ marg_top = Math.round(top + org_height + opts.edgeOffset);
166
+ }
167
+
168
+ if(t_class == "_right_top" || t_class == "_left_top"){
169
+ marg_top = marg_top + 5;
170
+ } else if(t_class == "_right_bottom" || t_class == "_left_bottom"){
171
+ marg_top = marg_top - 5;
172
+ }
173
+ if(t_class == "_left_top" || t_class == "_left_bottom"){
174
+ marg_left = marg_left + 5;
175
+ }
176
+ tiptip_arrow.css({"margin-left": arrow_left+"px", "margin-top": arrow_top+"px"});
177
+ tiptip_holder.css({"margin-left": marg_left+"px", "margin-top": marg_top+"px"}).attr("class","tip"+t_class);
178
+
179
+ if (timeout){ clearTimeout(timeout); }
180
+ timeout = setTimeout(function(){ tiptip_holder.stop(true,true).fadeIn(opts.fadeIn); }, opts.delay);
181
+ }
182
+
183
+ function deactive_tiptip(){
184
+ opts.exit.call(this);
185
+ if (timeout){ clearTimeout(timeout); }
186
+ tiptip_holder.fadeOut(opts.fadeOut);
187
+ }
188
+ }
189
+ });
190
+ }
191
+ })(jQuery);
assets/js/jquery-tiptip/jquery.tipTip.min.js CHANGED
@@ -17,4 +17,4 @@
17
  * This TipTip jQuery plug-in is dual licensed under the MIT and GPL licenses:
18
  * http://www.opensource.org/licenses/mit-license.php
19
  * http://www.gnu.org/licenses/gpl.html
20
- */!function(t){t.fn.tipTip=function(e){var o={activation:"hover",keepAlive:!1,maxWidth:"200px",edgeOffset:3,defaultPosition:"bottom",delay:400,fadeIn:200,fadeOut:200,attribute:"title",content:!1,enter:function(){},exit:function(){}},i=t.extend(o,e);if(t("#tiptip_holder").length<=0){(a=t('<div id="tiptip_holder"></div>')).css("max-width",i.maxWidth);var n=t('<div id="tiptip_content"></div>'),r=t('<div id="tiptip_arrow"></div>');t("body").append(a.html(n).prepend(r.html('<div id="tiptip_arrow_inner"></div>')))}else var a=t("#tiptip_holder"),n=t("#tiptip_content"),r=t("#tiptip_arrow");return this.each(function(){function e(){i.enter.call(this),n.html(d),a.hide().removeAttr("class").css("margin","0"),r.removeAttr("style");var e=parseInt(f.offset().top),o=parseInt(f.offset().left),p=parseInt(f.outerWidth()),l=parseInt(f.outerHeight()),h=a.outerWidth(),c=a.outerHeight(),s=Math.round((p-h)/2),_=Math.round((l-c)/2),v=Math.round(o+s),m=Math.round(e+l+i.edgeOffset),g="",b="",M=Math.round(h-12)/2;"bottom"==i.defaultPosition?g="_bottom":"top"==i.defaultPosition?g="_top":"left"==i.defaultPosition?g="_left":"right"==i.defaultPosition&&(g="_right");var w=s+o<parseInt(t(window).scrollLeft()),O=h+o>parseInt(t(window).width());w&&s<0||"_right"==g&&!O||"_left"==g&&o<h+i.edgeOffset+5?(g="_right",b=Math.round(c-13)/2,M=-12,v=Math.round(o+p+i.edgeOffset),m=Math.round(e+_)):(O&&s<0||"_left"==g&&!w)&&(g="_left",b=Math.round(c-13)/2,M=Math.round(h),v=Math.round(o-(h+i.edgeOffset+5)),m=Math.round(e+_));var x=e+l+i.edgeOffset+c+8>parseInt(t(window).height()+t(window).scrollTop()),I=e+l-(i.edgeOffset+c+8)<0;x||"_bottom"==g&&x||"_top"==g&&!I?("_top"==g||"_bottom"==g?g="_top":g+="_top",b=c,m=Math.round(e-(c+5+i.edgeOffset))):(I|("_top"==g&&I)||"_bottom"==g&&!x)&&("_top"==g||"_bottom"==g?g="_bottom":g+="_bottom",b=-12,m=Math.round(e+l+i.edgeOffset)),"_right_top"==g||"_left_top"==g?m+=5:"_right_bottom"!=g&&"_left_bottom"!=g||(m-=5),"_left_top"!=g&&"_left_bottom"!=g||(v+=5),r.css({"margin-left":M+"px","margin-top":b+"px"}),a.css({"margin-left":v+"px","margin-top":m+"px"}).attr("class","tip"+g),u&&clearTimeout(u),u=setTimeout(function(){a.stop(!0,!0).fadeIn(i.fadeIn)},i.delay)}function o(){i.exit.call(this),u&&clearTimeout(u),a.fadeOut(i.fadeOut)}var f=t(this);if(i.content)d=i.content;else var d=f.attr(i.attribute);if(""!=d){i.content||f.removeAttr(i.attribute);var u=!1;"hover"==i.activation?(f.hover(function(){e()},function(){i.keepAlive||o()}),i.keepAlive&&a.hover(function(){},function(){o()})):"focus"==i.activation?f.focus(function(){e()}).blur(function(){o()}):"click"==i.activation&&(f.click(function(){return e(),!1}).hover(function(){},function(){i.keepAlive||o()}),i.keepAlive&&a.hover(function(){},function(){o()}))}})}}(jQuery);
17
  * This TipTip jQuery plug-in is dual licensed under the MIT and GPL licenses:
18
  * http://www.opensource.org/licenses/mit-license.php
19
  * http://www.gnu.org/licenses/gpl.html
20
+ */(function(e){e.fn.tipTip=function(t){var n={activation:"hover",keepAlive:!1,maxWidth:"200px",edgeOffset:3,defaultPosition:"bottom",delay:400,fadeIn:200,fadeOut:200,attribute:"title",content:!1,enter:function(){},exit:function(){}},r=e.extend(n,t);if(e("#tiptip_holder").length<=0){var i=e('<div id="tiptip_holder" style="max-width:'+r.maxWidth+';"></div>'),s=e('<div id="tiptip_content"></div>'),o=e('<div id="tiptip_arrow"></div>');e("body").append(i.html(s).prepend(o.html('<div id="tiptip_arrow_inner"></div>')))}else var i=e("#tiptip_holder"),s=e("#tiptip_content"),o=e("#tiptip_arrow");return this.each(function(){var t=e(this);if(r.content)var n=r.content;else var n=t.attr(r.attribute);if(n!=""){r.content||t.removeAttr(r.attribute);var u=!1;if(r.activation=="hover"){t.hover(function(){a()},function(){r.keepAlive||f()});r.keepAlive&&i.hover(function(){},function(){f()})}else if(r.activation=="focus")t.focus(function(){a()}).blur(function(){f()});else if(r.activation=="click"){t.click(function(){a();return!1}).hover(function(){},function(){r.keepAlive||f()});r.keepAlive&&i.hover(function(){},function(){f()})}function a(){r.enter.call(this);s.html(n);i.hide().removeAttr("class").css("margin","0");o.removeAttr("style");var a=parseInt(t.offset().top),f=parseInt(t.offset().left),l=parseInt(t.outerWidth()),c=parseInt(t.outerHeight()),h=i.outerWidth(),p=i.outerHeight(),d=Math.round((l-h)/2),v=Math.round((c-p)/2),m=Math.round(f+d),g=Math.round(a+c+r.edgeOffset),y="",b="",w=Math.round(h-12)/2;r.defaultPosition=="bottom"?y="_bottom":r.defaultPosition=="top"?y="_top":r.defaultPosition=="left"?y="_left":r.defaultPosition=="right"&&(y="_right");var E=d+f<parseInt(e(window).scrollLeft()),S=h+f>parseInt(e(window).width());if(E&&d<0||y=="_right"&&!S||y=="_left"&&f<h+r.edgeOffset+5){y="_right";b=Math.round(p-13)/2;w=-12;m=Math.round(f+l+r.edgeOffset);g=Math.round(a+v)}else if(S&&d<0||y=="_left"&&!E){y="_left";b=Math.round(p-13)/2;w=Math.round(h);m=Math.round(f-(h+r.edgeOffset+5));g=Math.round(a+v)}var x=a+c+r.edgeOffset+p+8>parseInt(e(window).height()+e(window).scrollTop()),T=a+c-(r.edgeOffset+p+8)<0;if(x||y=="_bottom"&&x||y=="_top"&&!T){y=="_top"||y=="_bottom"?y="_top":y+="_top";b=p;g=Math.round(a-(p+5+r.edgeOffset))}else if(T|(y=="_top"&&T)||y=="_bottom"&&!x){y=="_top"||y=="_bottom"?y="_bottom":y+="_bottom";b=-12;g=Math.round(a+c+r.edgeOffset)}if(y=="_right_top"||y=="_left_top")g+=5;else if(y=="_right_bottom"||y=="_left_bottom")g-=5;if(y=="_left_top"||y=="_left_bottom")m+=5;o.css({"margin-left":w+"px","margin-top":b+"px"});i.css({"margin-left":m+"px","margin-top":g+"px"}).attr("class","tip"+y);u&&clearTimeout(u);u=setTimeout(function(){i.stop(!0,!0).fadeIn(r.fadeIn)},r.delay)}function f(){r.exit.call(this);u&&clearTimeout(u);i.fadeOut(r.fadeOut)}}})}})(jQuery);
assets/js/multiselect.js ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ jQuery(function(){
2
+ jQuery( '.job-manager-multiselect' ).chosen( job_manager_chosen_multiselect_args );
3
+ });
assets/js/term-multiselect.js ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ jQuery(function(){
2
+ jQuery( '.job-manager-category-dropdown' ).chosen( job_manager_chosen_multiselect_args );
3
+ });
changelog.txt DELETED
@@ -1,706 +0,0 @@
1
- = 1.31.2 =
2
- * Fix: Adds missing quote from WP admin taxonomy fields. (@redpik)
3
-
4
- = 1.31.1 =
5
- * Enhancement: Add option to show company logo in Recent Jobs widget. (@RajeebTheGreat)
6
- * Enhancement: Suggest additional cookie information on Privacy Policy page.
7
- * Enhancement: Add WPJM related meta data to user data extract.
8
- * Fix: Tightened the security of the plugin with additional string escaping.
9
- * Fix: Issue with map link in admin backend. (@RajeebTheGreat)
10
- * Fix: No longer auto-expire job listings in Draft status.
11
- * Fix: Issue with undefined index error in WP admin. (@albionselimaj)
12
- * Fix: Issue with duplicate usernames preventing submission of job listings. (@timothyjensen)
13
- * Dev: Widespread code formatting cleanup throughout the plugin.
14
-
15
- = 1.31.0 =
16
- * Change: Minimum WordPress version is now 4.7.0.
17
- * Enhancement: Add email notifications with initial support for new jobs, updated jobs, and expiring listings.
18
- * Enhancement: For GDPR, scrub WPJM data from database on uninstall if option is enabled.
19
- * Enhancement: Filter by Filled and Featured status in WP admin.
20
- * Enhancement: Simplify the display of application URLs.
21
- * Enhancement: When using WPML, prevent changes to page options when on a non-default language. (@vukvukovich)
22
- * Enhancement: Include company logo in structured data. (@RajeebTheGreat)
23
- * Enhancement: Use more efficient jQuery selectors in scripts. (@RajeebTheGreat)
24
- * Enhancement: Use proper `<h2>` tag in `content-summary-job_listing.php` template for the job title. (@abdullah1908)
25
- * Enhancement: Hide empty categories on `[job]` filter.
26
- * Fix: Update calls to `get_terms()` to use the new format.
27
- * Fix: Maintain the current tab when saving settings in WP Admin.
28
- * Fix: Enqueue the date picker CSS when used on the front-end.
29
- * Fix: Remove errors when widget instance was created without setting defaults.
30
- * REST API Pre-release: Add support for job category taxonomy endpoints.
31
- * Dev: Add `$job_id` parameter to `job_manager_job_dashboard_do_action_{$action}` action hook. (@jonasvogel)
32
- * Dev: Add support for hidden WPJM settings in WP Admin.
33
-
34
- = 1.30.2 =
35
- * Enhancement: Show notice when user is using an older version of WordPress.
36
- * Enhancement: Hide unnecessary view mode in WP Admin's Job Listings page. (@RajeebTheGreat)
37
- * Enhancement: Add support for the `paged` parameter in the RSS feed. (@RajeebTheGreat)
38
- * Fix: Minor PHP 7.2 compatibility fixes.
39
- * Dev: Allow `parent` attribute to be passed to `job_manager_dropdown_categories()`. (@RajeebTheGreat)
40
-
41
- = 1.30.1 =
42
- * Fix: Minor issue with a strict standard error being displayed on some instances.
43
-
44
- = 1.30.0 =
45
- * Enhancement: Adds ability to have a reCAPTCHA field to check if job listing author is human.
46
- * Enhancement: Allows for option to make edits to job listings force listing back into pending approval status.
47
- * Enhancement: Adds spinner and disables form when user submits job listing.
48
- * Enhancement: Update the add-ons page of the plugin.
49
- * Enhancement: Added the ability to sort jobs randomly on the Featured Jobs Widget.
50
- * Enhancement: Improved handling of alternative date formats when editing job expiration field in WP admin.
51
- * Enhancement: Added star indicator next to featured listings on `[job_dashboard]`.
52
- * Enhancement: Opt-in to usage tracking so we can better improve the plugin.
53
- * Enhancement: Introduced new asset enqueuing strategy that will be turned on in 1.32.0. Requires plugin and theme updates. (Dev notes: https://github.com/Automattic/WP-Job-Manager/pull/1354)
54
- * Fix: Use WordPress core checks for image formats to not confuse `docx` as an image. (@tripflex)
55
- * Fix: Issue with `[jobs]` shortcode when `categories` argument is provided.
56
- * Fix: Issue with double encoding HTML entities in custom text area fields.
57
- * Fix: Updates `job-dashboard.php` template with `colspan` fix on no active listings message.
58
- * Fix: Clear job listings cache when deleting a user and their job listings.
59
- * Dev: Adds `is_wpjm()` and related functions to test if we're on a WPJM related page.
60
- * Dev: Adds `job_manager_user_edit_job_listing` action that fires after a user edits a job listing.
61
- * Dev: Adds `job_manager_enable_job_archive_page` filter to enable job archive page.
62
- * Dev: Adds `date` field for custom job listing form fields.
63
-
64
- = 1.29.3 =
65
- * Fix: When retrieving job listing results, cache only the post results and not all of `WP_Query` (props slavco)
66
-
67
- = 1.29.2 =
68
- * Fix: PHP Notice when sanitizing multiple inputs (bug in 1.29.1 release). (@albionselimaj)
69
-
70
- = 1.29.1 =
71
- * Enhancement: When retrieving listings in `[jobs]` shortcode, setting `orderby` to `rand_featured` will still place featured listings at the top.
72
- * Enhancement: Scroll to show application details when clicking on "Apply for Job" button.
73
- * Change: Updates `account-signin.php` template to warn users email will be confirmed only if that is enabled.
74
- * Fix: Sanitize URLs and emails differently on the application method job listing field.
75
- * Fix: Remove PHP notice in Featured Jobs widget. (@himanshuahuja96)
76
- * Fix: String fix for consistent spelling of "license" when appearing in strings. (@garrett-eclipse)
77
- * Fix: Issue with paid add-on licenses not showing up when some third-party plugins were installed.
78
- * Dev: Runs new actions (`job_manager_recent_jobs_widget_before` and `job_manager_recent_jobs_widget_after`) inside Recent Jobs widget.
79
- * Dev: Change `wpjm_get_the_job_types()` to return an empty array when job types are disabled.
80
- * See all: https://github.com/Automattic/WP-Job-Manager/milestone/15?closed=1
81
-
82
- = 1.29.0 =
83
- * Enhancement: Moves license and update management for official add-ons to the core plugin.
84
- * Enhancement: Update language for setup wizard with more clear descriptions.
85
- * Fix: Prevent duplicate attachments to job listing posts for non-image media. (@tripflex)
86
- * Fix: PHP error on registration form due to missing placeholder text.
87
- * Fix: Apply `the_job_application_method` filter even when no default is available. (@turtlepod)
88
- * Fix: Properly reset category selector on `[jobs]` shortcode.
89
-
90
- = 1.28.0 =
91
- * Enhancement: Improves support for Google Job Search by adding `JobPosting` structured data.
92
- * Enhancement: Adds ability for job types to be mapped to an employment type as defined for Google Job Search.
93
- * Enhancement: Requests search engines no longer index expired and filled job listings.
94
- * Enhancement: Improves support with third-party sitemap generation in Jetpack, Yoast SEO, and All in One SEO.
95
- * Enhancement: Updated descriptions and help text on settings page.
96
- * Enhancement: Lower cache expiration times across plugin and limit use of autoloaded cache transients.
97
- * Fix: Localization issue with WPML in the [jobs] shortcode.
98
- * Fix: Show job listings' published date in localized format.
99
- * Fix: Job submission form allows users to select multiple job types when they go back a step.
100
- * Fix: Some themes that overloaded functions would break in previous release.
101
- * Dev: Adds versions to template files so it is easier to tell when they are updated.
102
- * Dev: Adds a new `wpjm_notify_new_user` action that allows you to override default behavior.
103
- * Dev: Early version of REST API is bundled but disabled by default. Requires PHP 5.3+ and `WPJM_REST_API_ENABLED` constant must be set to true. Do not use in production; endpoints may change. (@pkg)
104
-
105
- = 1.27.0 =
106
- * Enhancement: Admins can now allow users to specify an account password when posting their first job listing.
107
- * Enhancement: Pending job listing counts are now cached for improved WP Admin performance. (@tripflex)
108
- * Enhancement: Allows users to override permalink slugs in WP Admin's Permalink Settings screen.
109
- * Enhancement: Allows admins to perform bulk updating of jobs as filled/not filled.
110
- * Enhancement: Adds job listing status CSS classes on single job listings.
111
- * Enhancement: Adds `wpjm_the_job_title` filter for inserting non-escaped HTML alongside job titles in templates.
112
- * Enhancement: Allows admins to filter by `post_status` in `[jobs]` shortcode.
113
- * Enhancement: Allows accessing settings tab from hash in URL. (@tripflex)
114
- * Fix: Make sure cron jobs for checking/cleaning expired listings are always in place.
115
- * Fix: Better handling of multiple job types. (@spencerfinnell)
116
- * Fix: Issue with deleting company logos from job listings submission form.
117
- * Fix: Warning thrown on job submission form when user not logged in. (@piersb)
118
- * Fix: Issue with WPML not syncing some meta fields.
119
- * Fix: Better handling of AJAX upload errors. (@tripflex)
120
- * Fix: Remove job posting cookies on logout.
121
- * Fix: Expiration date can be cleared if default job duration option is empty. (@spencerfinnell)
122
- * Fix: Issue with Safari and expiration datepicker.
123
-
124
- = 1.26.2 =
125
- * Fix: Prevents use of Ajax file upload endpoint for visitors who aren't logged in. Themes should check with `job_manager_user_can_upload_file_via_ajax()` if using endpoint in templates.
126
- * Fix: Escape post title in WP Admin's Job Listings page and template segments. (Props to @EhsanCod3r)
127
-
128
- = 1.26.1 =
129
- * Enhancement: Add language using WordPress's current locale to geocode requests.
130
- * Fix: Allow attempts to use Google Maps Geocode API without an API key. (@spencerfinnell)
131
- * Fix: Issue affecting job expiry date when editing a job listing. (@spencerfinnell)
132
- * Fix: Show correct total count of results on `[jobs]` shortcode.
133
-
134
- = 1.26.0 =
135
- * Enhancement: Warn the user if they're editing an existing job.
136
- * Enhancement: WP Admin Job Listing page's table is now responsive. (@turtlepod)
137
- * Enhancement: New setting for hiding expired listings from `[jobs]` filter. (@turtlepod)
138
- * Enhancement: Use WP Query's built in search function to improve searching in `[jobs]`.
139
- * Fix: Job Listing filter only searches meta fields with relevant content. Add custom fields with `job_listing_searchable_meta_keys` filter. (@turtlepod)
140
- * Fix: Improved support for WPML and Polylang.
141
- * Fix: Expired field no longer forces admins to choose a date in the future. (@turtlepod)
142
- * Fix: Listings with expiration date in past will immediately expire; moving to Active status will extend if necessary. (@turtlepod)
143
- * Fix: Google Maps API key setting added to fix geolocation retrieval on new sites.
144
- * Fix: Issue when duplicating a job listing with a field for multiple file uploads. (@turtlepod)
145
- * Fix: Hide page results when adding links in the `[submit_job_form]` shortcode.
146
- * Fix: Job feed now loads when a site has no posts.
147
- * Fix: No error is thrown when deleting a user. (@tripflex)
148
- * Dev: Plugins and themes can now retrieve JSON of Job Listings results without HTML. (@spencerfinnell)
149
- * Dev: Updated inline documentation.
150
-
151
- = 1.25.3 =
152
- * Enhancement: Allow job types to be optional, just like categories. https://github.com/automattic/wp-job-manager/pull/789 Props Donncha.
153
- * Enhancement: Add get_job_listing_types filter. https://github.com/automattic/wp-job-manager/pull/824 Props Adam Heckler.
154
- * Enhancement: Various date format setting improvements. See https://github.com/automattic/wp-job-manager/pull/757 Props Christian Nolen.
155
- * Enhancement: Pass search values with the job_manager_get_listings_custom_filter_text filter. https://github.com/automattic/wp-job-manager/pull/845 Props Kraft.
156
- * Fix: Prevent a potential CSRF vector. https://github.com/automattic/wp-job-manager/pull/891 Props Jay Patel for the responsible disclosure.
157
- * Fix: Improve load time by removing unnecessary oEmbed call. https://github.com/automattic/wp-job-manager/pull/768 Props Myles McNamara.
158
- * Fix: Improve WPML compatability. https://github.com/automattic/wp-job-manager/pull/787 Props Spencer Finnell.
159
- * Fix: Add an implicit whitelist for API requests. https://github.com/automattic/wp-job-manager/pull/855 Props muddletoes.
160
- * Fix: Fixed taxonomy search conditions. See https://github.com/automattic/wp-job-manager/pull/859/ Props Jonas Vogel.
161
-
162
- = 1.25.2 =
163
- * Fix - The date format of the expiry date picker was incorrect in translations so we added a comment to clarify, and fixed translations.
164
- * Fix - Changing the date of an expired job would forget the date, even though the job would become active.
165
- * Fix - Site owner can allow jobs to have only one or more than one types.
166
- * Fix - Show expired jobs if that setting is enabled.
167
- * Fix - Simplify the search message on the jobs page to avoid translation problems.
168
- * Fix - The uploader would ignore WordPress created image sizes.
169
- * Fix - setup.css was loaded on all admin pages.
170
- * Fix - The preview of a listing could be edited or viewed by anyone. Props @tripflex
171
- * Fix - When users were deleted their jobs weren't.
172
- * Fix - OrderBy Rand wasn't working.
173
- * Dev - Add upload filters and update PHPDocs, props @tripflex
174
- * Fix - Stop using jQuery.live, props @tripflex
175
-
176
- = 1.25.1 =
177
- * Feature - Adds a view button to the Admin UI for easy access to submitted files or URLs. Props tripflex
178
- * Fix - Add hardening to file uploads to prevent accepting unexpected file times. Previously, other WP-allowed types were sometimes accepted.
179
- * Fix - Job post form categories are now properly cached and displayed per language when using WPML or Polylang.
180
- * Fix - Refactored WPML workaround, which was causing no job listings on non-default languages.
181
- * Fix - Allow employers to edit job listings when a listing is pending payment.
182
- * Fix - No longer display Job Taxonomies in the WordPress tag cloud.
183
- * Fix - Migrate away from jQuery.live, which is no longer supported.
184
- * Tweak - Updated incorrect settings description.
185
- * Dev - Adds hook to add items in a job's RSS feed item.
186
- * Dev - Adds filter to disable Job Listings cache
187
- * Dev - Inline docs and coding standards improvements.
188
-
189
- = 1.25.0 =
190
- * Feature - Ability to duplicate listings from job dashboard.
191
- * Fix - Support WP_EMBED in job descriptions.
192
- * Fix - Ensure logo is displayed on edit, before submission.
193
- * Fix - Attachment URLs on multisite.
194
- * Fix - Refactored WPML workaround, which was causing no job listings on non-default languages.
195
- * Fix - No need to decode URLs anymore https://core.trac.wordpress.org/ticket/23605.
196
- * Dev - submit_job_form_end/submit_job_form_start actions.
197
- * Dev - job-manager-datepicker class for backend date fields.
198
-
199
- = 1.24.0 =
200
- * Feature - Use featured images to store company logos.
201
- * Feature - Search term names for keywords.
202
- * Feature - Search custom fields in backend job listing search.
203
- * Tweak - Allow job expiry field to be localised.
204
- * Fix - The above change avoids creation of duplicate images in media library.
205
- * Dev - Added methods to WP_Job_Manager_Form; get_steps, get_step_key, set_step.
206
- * Dev - Made WP_Job_Manager_Form call the next 'handler' if no view is defined for the next step.
207
- * Dev - Added template to control job preview form.
208
-
209
- = 1.23.13 =
210
- * Fix - Conflict between the_job_location() and the regions plugin.
211
- * Tweak - Allow some HTML in the_job_location - uses wp_kses_post.
212
-
213
- = 1.23.12 =
214
- * Fix - Transient clear query.
215
- * Tweak - New user notification pluggable function.
216
- * Tweak - Use subquery in keyword search to avoid long queries.
217
- * Tweak - Only search for keywords of 2 or more characters.
218
- * Tweak - job_manager_get_listings_keyword_length_threshold filter.
219
- * Tweak - PolyLang compatibility functions.
220
- * Tweak - Unattach company logo when a new attachment is uploaded.
221
-
222
- = 1.23.11 =
223
- * Fix - Author check in job_manager_user_can_edit_job().
224
- * Tweak - Before deleting a job, delete its attachments.
225
- * Tweak - Show previews in backend if needed.
226
-
227
- = 1.23.10 =
228
- * Fix - Handle WP 4.3 signup notification.
229
- * Fix - Map mime types to those that WordPress knows.
230
- * Fix - Alert text color.
231
- * Fix - Searches containing special chars.
232
- * Tweak - Improved uploader error handling and updated library.
233
- * Tweak - Improve job_manager_user_can_post_job and job_manager_user_can_edit_job capability handling in job-submit.php
234
- * Tweak - Clear transients in batches of 500.
235
- * Tweak - Removed transifex and translations - translation will take place on https://translate.wordpress.org/projects/wp-plugins/wp-job-manager
236
-
237
- = 1.23.9 =
238
- * Fixed editing content with wp_editor. Can no longer be passed to function already escaped.
239
-
240
- = 1.23.8 =
241
- * Fix - Security: XSS issue in account signin.
242
- * Tweak - Update new account email text.
243
-
244
- = 1.23.7 =
245
- * Fix - 4.3 issue showing "Description is a required field" due to editor field.
246
- * Tweak - Default job_manager_delete_expired_jobs to false. Set to true to have expired jobs deleted automatically. More sensible default.
247
- * Tweak - job_manager_term_select_field_wp_dropdown_categories_args filter.
248
- * Tweak - Ajax WPML handling.
249
-
250
- = 1.23.6 =
251
- * Fix - job_manager_ajax_filters -> job_manager_ajax_file_upload in file upload script.
252
-
253
- = 1.23.5 =
254
- * Feature - Allow [job_summary] to output multiple listings via 'limit' parameter.
255
- * Feature - Added flowplayer support.
256
- * Fix - Special chars in feeds.
257
- * Fix - Permalinks with index.php inside.
258
- * Fix - Notice when saving job form.
259
- * Fix - PHP4 widget constructors (https://gist.github.com/chriscct7/d7d077afb01011b1839d).
260
- * Tweak - Allow translation of job_manager_dropdown_categories.
261
- * Tweak - Added handling for .job-manager-filter class.
262
- * Tweak - Added trailing slashes to ajax endpoints.
263
- * Tweak - Made videos responsive.
264
- * Tweak - job_manager_attach_uploaded_files filter.
265
-
266
- = 1.23.4 =
267
- * Tweak - In 1.21.0 we switched to GET ajax requests to leverage caching, however, due to the length of some queries this was causing 414 request URI too long errors in many environments. Reverted to POST to avoid this.
268
- * Tweak - flush_rewrite_rules after updates to ensure ajax endpoint exists.
269
- * Tweak - Use relative path for ajax endpoint to work around https/http.
270
-
271
- = 1.23.3 =
272
- * Fix - WPML integration with lang.
273
- * Tweak - Improved plugin activation code.
274
- * Tweak - Improved theme switch code.
275
- * Tweak - Search the entire meta field, not just from the start.
276
- * Tweak - Added some debugging code to ajax-filters to display in console.
277
-
278
- = 1.23.2 =
279
- * Fix - Send entire form data (listify workaround).
280
- * Fix - Set is_home false on ajax endpoint (listify workaround).
281
-
282
- = 1.23.1 =
283
- * Fix - Orderby featured should be "menu order, date", not "manu order, title".
284
- * Tweak - Remove duplicate data from form_data in filters JS.
285
- * Tweak - If index is -1 in filters JS, abort.
286
-
287
- = 1.23.0 =
288
- * Feature - Custom AJAX endpoints to reduce overhead of loading admin.
289
- * Feature - Support radio fields.
290
- * Fix - Video max width.
291
- * Tweak - Admin remove overflow hidden from data box.
292
- * Tweak - Update notice styling.
293
- * Tweak - Improve orderby. https://make.wordpress.org/core/2014/08/29/a-more-powerful-order-by-in-wordpress-4-0/
294
- * Tweak - nofollow apply links.
295
- * Tweak - Rename 'title' to 'job title' for clarity.
296
- * Tweak - submit_job_form_prefix_post_name_with_company filter.
297
- * Tweak - submit_job_form_prefix_post_name_with_location filter.
298
- * Tweak - submit_job_form_prefix_post_name_with_job_type filter.
299
- * Tweak - Improved job_feed searching.
300
- * Tweak - Improved transient cleaning.
301
-
302
- = 1.22.3 =
303
- * Fix frontend listing edits.
304
-
305
- = 1.22.2 =
306
- * Tweak - Set form actions to current page.
307
- * Fix - Video embeds.
308
- * Fix - Load textdomain before post types are registered.
309
-
310
- = 1.22.1 =
311
- * Fix - It's 2015, but some people are still running PHP 5.2. Compatibility fix.
312
-
313
- = 1.22.0 =
314
- * Tweak - Refactored form classes to be instance based rather than static. Reduction in code base.
315
- * Tweak - Admin styling of the job data panels.
316
- * Tweak - Admin styling of the status column.
317
- * Tweak - Better handling of the expiry field.
318
- * Tweak - Search _geolocation_state_long.
319
- * Tweak - Allow admin fields to have custom 'name'.
320
- * Tweak - Use wp_video_shortcode instead of oembed directly.
321
- * Tweak - Tweak menu order code for featured jobs - use -1 for featured and leave other jobs alone.
322
- * Tweak - Clear expired date when publishing an expired listing.
323
- * Tweak - clear_expired_transients function.
324
- * Fix - Prevent term-checklist being disabled for guests.
325
- * Fix - Add WPML var to transient name.
326
- * Fix - Remove use of create_function.
327
-
328
- = 1.21.4 =
329
- * Fix - get_job_listings_keyword_search keyword search.
330
- * Fix - Clear term cache when terms are set for any object.
331
- * Fix - Legacy uploads.
332
- * Tweak - RTL improvements.
333
- * Tweak - Use RLIKE to search keywords in content.
334
- * Tweak - Show relative pagination.
335
- * Updated translations.
336
- * Arabic translation by Mamdouh Samy.
337
-
338
- = 1.21.3 =
339
- * Feature - Support posts_per_page in feed.
340
- * Fix - Correctly set menu_order when creating a new job, or updating featured status.
341
- * Fix - Updater when there are no featured jobs.
342
- * Fix - Add geolocation_street_number to clear_location_data.
343
-
344
- = 1.21.2 =
345
- * Fix - Remove requried attribute from file input.
346
-
347
- = 1.21.1 =
348
- * Fix - Remove file type check when field is not required and empty.
349
- * Fix - Hide "Applications have closed" for previews.
350
-
351
- = 1.21.0 =
352
- * Feature - Ajax loading history - back button will take you back to current position in the search. If you are on > page 1, a 'load previous' button will be shown so you can paginate either way.
353
- * Feature - Ajax file upload during job submission.
354
- * Feature - Cookie set when submitting a job to allow resuming if you leave the page.
355
- * Feature - job_apply shortcode to show application area in other places on your site.
356
- * Feature - Allow admin fields to be priority sorted.
357
- * Feature - Featured job widget.
358
- * Feature - Scroll to top on pagination click.
359
- * Feature - Option to "Hide content within expired listings". If disabled, expired listings will be listed normally with applications disabled.
360
- * Fix - Prevent attachments being uploaded several times.
361
- * Fix - Expiry date on first save.
362
- * Fix - Relist should go back to form.
363
- * Fix - jquery.com CDN for CSS.
364
- * Tweak - Geocode street and street number separately.
365
- * Tweak - File upload field markup.
366
- * Tweak - Added filters around taxonomy definition.
367
- * Tweak - Chanced search logic/query to use the new meta queries in 4.1.
368
- * Tweak - Implement transient caching for searches.
369
- * Tweak - Removed wp_dropdown_user due to performance concerns.
370
- * Tweak - Use menu_order to make featured listings sticky. Improves performance.
371
- * Tweak - Retrieve AJAX jobs with GET rather than POST request to take advantage of more caching. Plugins looking for POST data will need to update to look for GET/REQUEST instead.
372
- * Tweak - Prevent themes that (sigh) mess with content hooks from breaking inputs.
373
- * Tweak - Remove unused job-category field.
374
- * Tweak - Hide company div if company name missing.
375
-
376
- = 1.20.1 =
377
- * Fix - Core template overrides.
378
- * Updated localisations.
379
-
380
- = 1.20.0 =
381
- * Feature - Sortable location column in admin.
382
- * Feature - Automatically Generate Username from Email Address option (disable to show a username field).
383
- * Feature - 'filled' option for job shortcode to show all filled/non filled jobs.
384
- * Fix - Pagination with default permalinks.
385
- * Fix - Correctly generate geolocation data when adding a post manually.
386
- * Fix - Chosen width when resizing the page.
387
- * Fix - Show no jobs when all types de-selected.
388
- * Tweak - content-widget-no-jobs-found.php template file.
389
- * Tweak - Don't limit keyword search query functions to published jobs.
390
- * Tweak - job_manager_output_jobs_no_results action.
391
- * Tweak - No results template hooked into job_manager_output_jobs_no_results.
392
- * Tweak - Changed content-no-jobs-found.php content to work for ajax and static lists of jobs. Tweaked text.
393
- * Tweak - job_manager_default_company_logo filter for changing default company image.
394
- * Tweak - Enhance multiselect field with chosen.
395
- * Dev - Abiltiy to pass shortcode args to submit_job_form shortcode.
396
- * Dev - Made get_job_manager_template_part() use locate_job_manager_template().
397
- * Dev - Changed how username/email/role are passed to wp_job_manager_create_account (backwards compat).
398
-
399
- = 1.19.0 =
400
- * Feature - Added html5 required attribute to required fields.
401
- * Feature - Added compatibility with RP4WP.
402
- * Fix - Chosen RTL.
403
- * Fix - Addded additonal check to check edit capabilities.
404
- * Fix - Add correct step input to submission form.
405
- * Tweak - Add CSS class to 'showing' bar when shoing all results (no filters).
406
- * Tweak - Geocode, use sublocality_level_1 as city.
407
- * Tweak - Don't update slug when editing via the frontend.
408
- * Tweak - Set default meta data for new jobs.
409
- * Tweak - Add geolocation data after import with WP ALL Import.
410
- * Tweak - Filter to disable chosen: job_manager_chosen_enabled
411
- * Tweak - Login link on job dashboard. job-dashboard-login.php template file.
412
- * Tweak - Made backend management honour capabilities of users. Props to minderdl.
413
-
414
- = 1.18.0 =
415
- * Fix - Keep post name when pending job is posted by non-admin.
416
- * Fix - Prevent special chars breaking the feeds.
417
- * Tweak - Added new capabilities for all aspects of Job Listing Management. e.g. edit_job_listings, add_job_listing etc etc. Admin role will be updated on activation/upgrade. If you use custom roles, you'll need to edit them to grant access to the parts you wish.
418
- * Tweak - Improved uninstaller.
419
- * Tweak - Clear location data should include geolocation_postcode.
420
- * Tweak - Always show 'showing' bar, but conditonally show 'reset' link.
421
- * Tweak - Trigger geolocation whenever location field is saved, even by 3rd parties.
422
-
423
- = 1.17.0 =
424
- * Feature - job_manager_user_can_edit_pending_submissions function and setting.
425
- * Feature - Job summary shortcode - support random display of job (featured or non featured).
426
- * Feature - In admin, when clicking an author name show all jobs for that author.
427
- * Tweak - Added sanitization function to form class to handle strings and arrays.
428
-
429
- = 1.16.1 =
430
- * Fix - Use triggerHandler() instead of trigger() in ajax-filters to prevent events bubbling up.
431
- * Fix - Append current 'lang' to AJAX calls for WPML.
432
- * Fix - When specifying categories on the jobs shortcode, don't clear those categories on reset.
433
- * Tweak - Added job_manager_admin_screen_ids filter.
434
-
435
- = 1.16.0 =
436
- * Added setup wizard for new installs that creates pages/shortcodes automatically.
437
- * Added job_manager_get_permalink function.
438
- * Fix - Only show website link when actually set.
439
- * Fix - Only validate application when field is set.
440
- * Tweak - Added description to all fields.
441
- * Tweak - Added better setting fields for defining which pages contain the shortcodes.
442
- * Tweak - Nofollow website links.
443
- * Tweak - Removed font-sizes from default CSS and fixed display in default WP themes.
444
-
445
- = 1.15.0 =
446
- * Added location/keyword option to recent jobs widget.
447
- * Added job-listings-start and job-listings-end.php templates for customisation the wrapping elements.
448
- * Added filter type option for job categories. Can be set to require matching to any or all selected categories.
449
- * Added checkbox field type for forms.
450
- * Made backend application email field default to logged in user.
451
- * Fix - job_manager_get_resized_image to not error when it cannot read the image file.
452
- * Fix - Make admin_url relative.
453
- * Fix - Chosen CSS cutting off the placeholder in Firefox.
454
- * Fix - Added _company_video in backend.
455
-
456
- = 1.14.0 =
457
- * Extra filters for the filters template.
458
- * Changed text strings for easier customisations based on post type labels, and made some strings more generic.
459
- * New field types - term-checklist, term-multiselect, term-select. These save terms only.
460
- * Added chosen javascript to enhance multiselect boxes when needed.
461
- * Multiple category support.
462
- * Attach Images on Upload to the job posts.
463
- * Added an option to show a multiselect for categories on the job filters.
464
- * Enabled chosen() for the job category filter.
465
- * Added content-single-job_listing-company.php and content-single-job_listing-meta.php templates. These are hooked in.
466
- * Added optional company video field to submission form.
467
- * Video appended to company information box.
468
- * the_company_video() and get_the_company_video() functions.
469
- * show_more="false" for jobs shortcode to prevent loading more jobs.
470
- * job_manager_delete_expired_jobs filter (__return_false to disable expired job deletion).
471
- * job_manager_delete_expired_jobs_days filter (set number of days before deletion - default is 30).
472
- * Support HTML5 multiple attribute for file upload field. Pass multiple=>'true' to form field definition to enable.
473
- * Later loading for template functions.
474
-
475
- = 1.13.0 =
476
- * Shortcode arg to show numbered pagination instead of 'load more jobs'. show_pagination argument.
477
- * Define support for Jetpack publicize.
478
- * Show company name alt text for company logo.
479
- * Sort jobs by title, date, expirey date.
480
- * Added noscript element for jobs shortcode.
481
- * filter_var to validate URLs on the job submission form.
482
-
483
- = 1.12.1 =
484
- * Job submission form categories must not hide empty categories.
485
-
486
- = 1.12.0 =
487
- * On the job submission form, display hierarchical categories.
488
- * Use job_manager_ prefixed hooks for registration (register_post/registration_errors/register_form) to prevent issues with Captcha plugins.
489
- * Pass $post to job_manager_application_email_subject
490
- * Additonal hooks in job-filters template, and moved out job types output to separate template file.
491
- * Option to set the job dashboard page slug so plugins know where to look.
492
- * Allow you@yourdomain.com to be translated.
493
- * Make taxonomies hidden unless current_theme_supports( 'job-manager-templates' )
494
- * Adjusted job application area styling and added some additonal filters.
495
- * Improve backend order status selection.
496
- * Added some responsive styles for job lists.
497
- * Allow users to relist a job from the frontend. (Ensure WC Paid Listings and Simple Paid Listings are updated to support this).
498
-
499
- = 1.11.1 =
500
- * Fix ajax filters 'true' for show_filters
501
- * Fix geocoding for certain address strings
502
- * Fix keywords typo
503
- * Remove deprecated $wpdb->escape calls. Replaced with esc_sql
504
-
505
- = 1.11.0 =
506
- * Switch geocode to json and improve error checking.
507
- * If query limit is reached, stop making requests for a while.
508
- * Added extra data inside job_feed.
509
- * Few extra icons in font.
510
- * Additonal hooks in single template.
511
- * Pick up search_category from querystring to set default/selected category.
512
- * Ability to define selected_job_types for the jobs shortcode which will set the default job type filters.
513
- * Took out show_featured_only arg for the [jobs] shortcode and added 'featured' which can be set to true or false to show or hide featured jobs, or left null to show both.
514
- * Removed nonce from frontend job submission form to prevent issues with caching.
515
-
516
- = 1.10.0 =
517
- * Trigger change on 'enter' when filtering jobs.
518
- * Updated add-ons page to pull from wpjobmanager.com.
519
- * Updated links.
520
- * Fixed support for custom upload URLs.
521
- * Choose/limit application method to email, url or either.
522
- * Default application value (if logged in) set to user's email address.
523
- * show_featured_only option for [jobs] shortcode.
524
- * Add required-field class around required inputs.
525
- * Enable paste as text in wp-editor field.
526
-
527
- = 1.9.3 =
528
- * Fix email URLs.
529
- * Target blank for application URLs.
530
- * Add posted by (author) setting in backend.
531
- * When saving jobs, ensure _featured and _filled are set.
532
- * Load admin scripts conditionally.
533
-
534
- = 1.9.2 =
535
- * Fix missing parameter in application_details_url causing URLs to be missing when applying.
536
-
537
- = 1.9.1 =
538
- * Removed resource heavy 'default_meta' function from the installation process.
539
-
540
- = 1.9.0 =
541
- * Template - Split off URL and email application methods and added new hooks. This allows other plugins to manipulate the content.
542
- * Pass $values to edit job save function so permalinks are preserved.
543
- * When showing filters, ensure we check by slug if category is non-numeric.
544
- * Give listings ul a min height so that loading image is visible.
545
- * content-no-jobs-found.php template.
546
- * Fix apostrophe direction in signin template.
547
- * Bulk expire jobs.
548
- * submit_job_form_required_label hook.
549
- * ability to set default state for selects on submit form.
550
- * allow passed in classes in get_job_listing_class function.
551
- * Hook in the content only if in_the_loop(). Fixes issues with jobify and yoast SEO.
552
- * Removed .clear mixin to prevent theme conflicts.
553
-
554
- = 1.8.2 =
555
- * For initial load, target all .job_filters areas. Jobify compat.
556
-
557
- = 1.8.1 =
558
- * Fix - Corrected check to see if any category terms with jobs exist
559
-
560
- = 1.8.0 =
561
- * Feature - Take search/location vars from the querystring if set
562
- * Feature - Option to choose role for registration, and added an 'employer' role.
563
- * Feature - Support for comma separated keywords when searching
564
- * Fix - Use add_post_meta when editing a job to maintain featured status
565
- * Fix - category ordering
566
- * Fix - searching for keyword + location at the same time
567
- * Fix - Only show categories select box when they exist
568
- * Dev - job_manager_application_email_subject filter
569
-
570
- = 1.7.3 =
571
- * Some changes to file uploads to support custom mime types
572
- * Updated icon file (http://fontello.com/)
573
- * Fix category rss links
574
- * When doing a location search, search geolocation data
575
- * Fix notices when removing all company fields
576
- * Made jslint happy with ajax-filters.js
577
- * Use get_option( 'default_role' ) when creating a user
578
- * Grunt for release
579
-
580
- = 1.7.2 =
581
- * Preserve line breaks when saving textarea fields in admin
582
- * Hide 'showing all x' when no filters are chosen.
583
- * Register 'preview' status so that the counts are correct.
584
- * Delete previews via cron job.
585
-
586
- = 1.7.1 =
587
- * Updated textdomain to wp-job-manager
588
- * Re-done .pot file
589
- * Additonal filters for ajax responses
590
- * Moved localisations to Transifex https://www.transifex.com/projects/p/wp-job-manager/
591
-
592
- = 1.7.0 =
593
- * Added geolocation to save location data to meta after posting or saving a job. This will be used by other plugins.
594
- * Filter job_manager_geolocation_enabled and return false to turn off geolocation features.
595
- * Jobs shortcode can now be passed 'location' and 'keywords' to set the default for filters, or show only jobs with those keywords if filters are disabled
596
- * Html fix in widget
597
- * Add border around wp editor
598
- * Fix company logo in firefox
599
- * submit_job_form_wp_editor_args filter
600
- * "Empty" categories are visible when filtering jobs in admin.
601
-
602
- = 1.6.0 =
603
- * MP6/WP 3.8 optimised styling. Min version 3.8 for new styling.
604
- * Removed images previously used in admin.
605
- * Tweak the_company_logo() to check if logo is valid URL.
606
- * Replaced Genericons with custom set
607
- * Only show link to view job on dashboard when published
608
-
609
- = 1.5.2 =
610
- * Fix wp-editor field
611
- * Fix editing job images
612
-
613
- = 1.5.1 =
614
- * Changed get_the_time to get_post_time
615
- * Added textarea and wp-editor to form api
616
- * When using the job submit form, generate a more unqiue slug for the job - company-location-type-job-title
617
- * Ability to remove image from job submission form
618
- * Update icon font
619
- * Fix job_types filters
620
- * Field_select in admin
621
- * Fix access control on job editing
622
- * Job forms multiselect support
623
-
624
- = 1.5.0 =
625
- * Ability to edit job expiration date manually via admin
626
- * Settings API: Password field
627
- * Frontend Forms: Password field
628
- * Correctly turn off expiration when 'days' is not set
629
- * Greek should be el_GR
630
- * Settings: Use key for tabs - fixes issues with locales
631
- * Show pending count in admin menu
632
- * Added job_types argument to jobs shortcode to show jobs of a certain type only
633
- * Hierarchical dropdown for categories on filter form
634
- * job_manager_job_submitted hook in submission form
635
-
636
- = 1.4.0 =
637
- * Added pagination to the job dashboard to avoid memory issues
638
- * Schema.org markup for job listings
639
- * Greek translation by Ioannis Arsenis
640
-
641
- = 1.3.1 =
642
- * Remove line breaks from markup to prevent theme issues
643
-
644
- = 1.3.0 =
645
- * When using the [jobs] shortcode without filters, if jobs > per-page show the 'load more' link
646
- * Clearfix for meta div
647
- * Hooked up $size option for company logos
648
- * submit_job_form_save_job_data filter
649
- * Italian translation
650
- * Brazillian Portuguese translation
651
- * Respect other plugin columns in admin
652
- * Re-arranged admin columns to show less non-useful data
653
-
654
- = 1.2.0 =
655
- * Support for featured job listings
656
- * Support for meta job duration
657
- * set_expirey when publishing jobs from admin manually
658
- * Update handler
659
-
660
- = 1.1.3 =
661
- * Corrected form field label
662
- * Added french translation by Remi Corson
663
-
664
- = 1.1.2 =
665
- * job_manager_get_dashboard_jobs_args filter
666
- * Better handling of submit job steps.
667
- * Option to store the slug of the submit job page - used by addons.
668
- * Use :input in JS to support multiple input types if customised.
669
-
670
- = 1.1.1 =
671
- * Improved accuracy of job search
672
- * Fixed category filter dropdown in admin
673
-
674
- = 1.1.0 =
675
- * Tweaked css clearfixes
676
- * Use built in antispambot for encoding email.
677
- * job_manager_job_filters_showing_jobs_links filter
678
- * IE8 Apply filters JS fix
679
- * Fix spanish locale
680
- * Fixed strict standards errors
681
- * Improve 2013 Styles
682
- * Addons page. Disabled usings add_filter( 'job_manager_show_addons_page', '__return_false' );
683
-
684
- = 1.0.5 =
685
- * Added function to get listings by certain criteria.
686
- * Added ES translation.
687
- * Fix job feed when no args are present.
688
-
689
- = 1.0.4 =
690
- * More hooks in the submit process.
691
- * Hide apply button if url/email is unset.
692
-
693
- = 1.0.3 =
694
- * Some extra hooks in job-filters.php
695
- * Added a workaround for scripts which bork placeholders inside the job filters.
696
-
697
- = 1.0.2 =
698
- * Action in update_job_data() to allow saving of extra fields.
699
- * Added German translation by Chris Penning
700
-
701
- = 1.0.1 =
702
- * Slight tweak to listing field filters in admin.
703
- * 'attributes' argument for admin settings.
704
-
705
- = 1.0.0 =
706
- * First stable release.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/3rd-party/3rd-party.php DELETED
@@ -1,11 +0,0 @@
1
- <?php
2
- /**
3
- * Load 3rd party compatibility tweaks.
4
- */
5
- require_once JOB_MANAGER_PLUGIN_DIR . '/includes/3rd-party/jetpack.php';
6
- require_once JOB_MANAGER_PLUGIN_DIR . '/includes/3rd-party/wpml.php';
7
- require_once JOB_MANAGER_PLUGIN_DIR . '/includes/3rd-party/polylang.php';
8
- require_once JOB_MANAGER_PLUGIN_DIR . '/includes/3rd-party/yoast.php';
9
- require_once JOB_MANAGER_PLUGIN_DIR . '/includes/3rd-party/all-in-one-seo-pack.php';
10
- require_once JOB_MANAGER_PLUGIN_DIR . '/includes/3rd-party/rp4wp.php';
11
- require_once JOB_MANAGER_PLUGIN_DIR . '/includes/3rd-party/wp-all-import.php';
 
 
 
 
 
 
 
 
 
 
 
includes/3rd-party/all-in-one-seo-pack.php DELETED
@@ -1,25 +0,0 @@
1
- <?php
2
- /**
3
- * Adds additional compatibility with All in One SEO Pack.
4
- *
5
- * @package wp-job-manager
6
- */
7
-
8
- /**
9
- * Skip filled job listings.
10
- *
11
- * @param WP_Post[] $posts
12
- * @return WP_Post[]
13
- */
14
- function wpjm_aiosp_sitemap_filter_filled_jobs( $posts ) {
15
- foreach ( $posts as $index => $post ) {
16
- if ( $post instanceof WP_Post && 'job_listing' !== $post->post_type ) {
17
- continue;
18
- }
19
- if ( is_position_filled( $post ) ) {
20
- unset( $posts[ $index ] );
21
- }
22
- }
23
- return $posts;
24
- }
25
- add_action( 'aiosp_sitemap_post_filter', 'wpjm_aiosp_sitemap_filter_filled_jobs', 10, 3 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/3rd-party/jetpack.php DELETED
@@ -1,38 +0,0 @@
1
- <?php
2
- /**
3
- * Adds additional compatibility with Jetpack.
4
- *
5
- * @package wp-job-manager
6
- */
7
-
8
- /**
9
- * Skip filled job listings.
10
- *
11
- * @param bool $skip_post
12
- * @param WP_Post $post
13
- * @return bool
14
- */
15
- function wpjm_jetpack_skip_filled_job_listings( $skip_post, $post ) {
16
- if ( 'job_listing' !== $post->post_type ) {
17
- return $skip_post;
18
- }
19
-
20
- if ( is_position_filled( $post ) ) {
21
- return true;
22
- }
23
-
24
- return $skip_post;
25
- }
26
- add_action( 'jetpack_sitemap_skip_post', 'wpjm_jetpack_skip_filled_job_listings', 10, 2 );
27
-
28
- /**
29
- * Add `job_listing` post type to sitemap.
30
- *
31
- * @param array $post_types
32
- * @return array
33
- */
34
- function wpjm_jetpack_add_post_type( $post_types ) {
35
- $post_types[] = 'job_listing';
36
- return $post_types;
37
- }
38
- add_filter( 'jetpack_sitemap_post_types', 'wpjm_jetpack_add_post_type' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/3rd-party/polylang.php DELETED
@@ -1,67 +0,0 @@
1
- <?php
2
- /**
3
- * Only load these if Polylang plugin is installed and active.
4
- *
5
- * @package wp-job-manager
6
- */
7
-
8
- /**
9
- * Load routines only if Polylang is loaded.
10
- *
11
- * @since 1.26.0
12
- */
13
- function polylang_wpjm_init() {
14
- add_filter( 'wpjm_lang', 'polylang_wpjm_get_job_listings_lang' );
15
- add_filter( 'wpjm_page_id', 'polylang_wpjm_page_id' );
16
- add_action( 'get_job_listings_query_args', 'polylang_wpjm_query_language' );
17
- }
18
- add_action( 'pll_init', 'polylang_wpjm_init' );
19
-
20
-
21
- /**
22
- * Sets the current language when running job listings query.
23
- *
24
- * @since 1.29.1
25
- *
26
- * @param array $query_args
27
- * @return array
28
- */
29
- function polylang_wpjm_query_language( $query_args ) {
30
- if ( isset( $_POST['lang'] ) ) {
31
- $query_args['lang'] = $_POST['lang'];
32
- }
33
- return $query_args;
34
- }
35
-
36
- /**
37
- * Returns Polylang's current language.
38
- *
39
- * @since 1.26.0
40
- *
41
- * @param string $lang
42
- * @return string
43
- */
44
- function polylang_wpjm_get_job_listings_lang( $lang ) {
45
- if ( function_exists( 'pll_current_language' )
46
- && function_exists( 'pll_is_translated_post_type' )
47
- && pll_is_translated_post_type( 'job_listing' ) ) {
48
- return pll_current_language();
49
- }
50
- return $lang;
51
- }
52
-
53
- /**
54
- * Returns the page ID for the current language.
55
- *
56
- * @since 1.26.0
57
- *
58
- * @param int $page_id
59
- * @return int
60
- */
61
- function polylang_wpjm_page_id( $page_id ) {
62
- if ( function_exists( 'pll_get_post' ) ) {
63
- $page_id = pll_get_post( $page_id );
64
- }
65
- return absint( $page_id );
66
- }
67
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/3rd-party/rp4wp.php DELETED
@@ -1,56 +0,0 @@
1
- <?php
2
- /**
3
- * Adds additional compatibility with Related Post for WordPress.
4
- *
5
- * @package wp-job-manager
6
- */
7
-
8
- add_filter( 'rp4wp_get_template', 'wpjm_rp4wp_template', 10, 3 );
9
- add_filter( 'rp4wp_related_meta_fields', 'wpjm_rp4wp_related_meta_fields', 10, 3 );
10
- add_filter( 'rp4wp_related_meta_fields_weight', 'wpjm_rp4wp_related_meta_fields_weight', 10, 3 );
11
-
12
- /**
13
- * Replaces RP4WP template with the template from Job Manager.
14
- *
15
- * @param string $located
16
- * @param string $template_name
17
- * @param array $args
18
- * @return string
19
- */
20
- function wpjm_rp4wp_template( $located, $template_name, $args ) {
21
- if ( 'related-post-default.php' === $template_name && 'job_listing' === $args['related_post']->post_type ) {
22
- return JOB_MANAGER_PLUGIN_DIR . '/templates/content-job_listing.php';
23
- }
24
- return $located;
25
- }
26
-
27
- /**
28
- * Adds meta fields for RP4WP to relate jobs by.
29
- *
30
- * @param array $meta_fields
31
- * @param int $post_id
32
- * @param WP_Post $post
33
- * @return array
34
- */
35
- function wpjm_rp4wp_related_meta_fields( $meta_fields, $post_id, $post ) {
36
- if ( 'job_listing' === $post->post_type ) {
37
- $meta_fields[] = '_company_name';
38
- $meta_fields[] = '_job_location';
39
- }
40
- return $meta_fields;
41
- }
42
-
43
- /**
44
- * Adds meta fields for RP4WP to relate jobs by.
45
- *
46
- * @param int $weight
47
- * @param WP_Post $post
48
- * @param string $meta_field
49
- * @return int
50
- */
51
- function wpjm_rp4wp_related_meta_fields_weight( $weight, $post, $meta_field ) {
52
- if ( 'job_listing' === $post->post_type ) {
53
- $weight = 100;
54
- }
55
- return $weight;
56
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/3rd-party/wp-all-import.php DELETED
@@ -1,25 +0,0 @@
1
- <?php
2
- /**
3
- * Adds additional compatibility with WP All Import.
4
- *
5
- * @package wp-job-manager
6
- */
7
-
8
- add_action( 'pmxi_saved_post', 'wpjm_pmxi_saved_post', 10, 1 );
9
-
10
- /**
11
- * After importing via WP All Import, adds default meta data.
12
- *
13
- * @param int $post_id
14
- */
15
- function wpjm_pmxi_saved_post( $post_id ) {
16
- if ( 'job_listing' === get_post_type( $post_id ) ) {
17
- WP_Job_Manager_Post_Types::instance()->maybe_add_default_meta_data( $post_id );
18
- if ( ! WP_Job_Manager_Geocode::has_location_data( $post_id ) ) {
19
- $location = get_post_meta( $post_id, '_job_location', true );
20
- if ( $location ) {
21
- WP_Job_Manager_Geocode::generate_location_data( $post_id, $location );
22
- }
23
- }
24
- }
25
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/3rd-party/wpml.php DELETED
@@ -1,103 +0,0 @@
1
- <?php
2
- /**
3
- * Only load these if WPML plugin is installed and active.
4
- *
5
- * @package wp-job-manager
6
- */
7
-
8
- /**
9
- * Load routines only if WPML is loaded.
10
- *
11
- * @since 1.26.0
12
- */
13
- function wpml_wpjm_init() {
14
- add_action( 'get_job_listings_init', 'wpml_wpjm_set_language' );
15
- add_filter( 'wpjm_lang', 'wpml_wpjm_get_job_listings_lang' );
16
- add_filter( 'wpjm_page_id', 'wpml_wpjm_page_id' );
17
-
18
- $default_lang = apply_filters( 'wpml_default_language', null );
19
- $current_lang = apply_filters( 'wpml_current_language', null );
20
-
21
- // Add filter only for non default languages.
22
- if ( $current_lang !== $default_lang ) {
23
- add_filter( 'job_manager_settings', 'wpml_wpjm_hide_page_selection' );
24
- }
25
- }
26
-
27
- add_action( 'wpml_loaded', 'wpml_wpjm_init' );
28
- add_action( 'wpml_loaded', 'wpml_wpjm_set_language' );
29
-
30
- /**
31
- * Sets WPJM's language if it is sent in the Ajax request.
32
- * Note: This is hooked into both `wpml_loaded` and `get_job_listings_init`. As of WPML 3.7.1, if it was hooked
33
- * into just `wpml_loaded` the query doesn't get the correct language for job listings. If it is just hooked into
34
- * `get_job_listings_init` the locale doesn't get set correctly and the string translations are only loaded from
35
- * the default language.
36
- *
37
- * @since 1.26.0
38
- */
39
- function wpml_wpjm_set_language() {
40
- if ( ( strstr( $_SERVER['REQUEST_URI'], '/jm-ajax/' ) || ! empty( $_GET['jm-ajax'] ) ) && isset( $_POST['lang'] ) ) {
41
- do_action( 'wpml_switch_language', sanitize_text_field( $_POST['lang'] ) );
42
- }
43
- }
44
-
45
- /**
46
- * Returns WPML's current language.
47
- *
48
- * @since 1.26.0
49
- *
50
- * @param string $lang
51
- *
52
- * @return string
53
- */
54
- function wpml_wpjm_get_job_listings_lang( $lang ) {
55
- return apply_filters( 'wpml_current_language', $lang );
56
- }
57
-
58
- /**
59
- * Returns the page ID for the current language.
60
- *
61
- * @param int $page_id
62
- *
63
- * @return int
64
- */
65
- function wpml_wpjm_page_id( $page_id ) {
66
- return apply_filters( 'wpml_object_id', $page_id, 'page', true );
67
- }
68
-
69
- /**
70
- * Set WPJM page options to hidden for non default languages.
71
- *
72
- * @since 1.31.0
73
- *
74
- * @param array $settings
75
- *
76
- * @return array
77
- */
78
- function wpml_wpjm_hide_page_selection( $settings ) {
79
- foreach ( $settings['job_pages'][1] as $key => $setting ) {
80
- if ( 'page' !== $setting['type'] ) {
81
- continue;
82
- }
83
- $setting['type'] = 'hidden';
84
- $setting['human_value'] = __( 'Page Not Set', 'wp-job-manager' );
85
- $current_value = get_option( $setting['name'] );
86
- if ( $current_value ) {
87
- $page = get_post( apply_filters( 'wpml_object_id', $current_value, 'page' ) );
88
-
89
- if ( $page ) {
90
- $setting['human_value'] = $page->post_title;
91
- }
92
- }
93
-
94
- $default_lang = apply_filters( 'wpml_default_language', null );
95
- $url_to_edit_page = admin_url( 'edit.php?post_type=job_listing&page=job-manager-settings&lang=' . $default_lang . '#settings-job_pages' );
96
-
97
- // translators: Placeholder (%s) is the URL to edit the primary language in WPML.
98
- $setting['desc'] = sprintf( __( '<a href="%s">Switch to primary language</a> to edit this setting.', 'wp-job-manager' ), $url_to_edit_page );
99
- $settings['job_pages'][1][ $key ] = $setting;
100
- }
101
-
102
- return $settings;
103
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/3rd-party/yoast.php DELETED
@@ -1,29 +0,0 @@
1
- <?php
2
- /**
3
- * Adds additional compatibility with Yoast SEO.
4
- *
5
- * Yoast SEO will by default include the `job_listing` post type because it is flagged as public.
6
- *
7
- * @package wp-job-manager
8
- */
9
-
10
- /**
11
- * Skip filled job listings.
12
- *
13
- * @param array $url Array of URL parts.
14
- * @param string $type URL type.
15
- * @param object $post Post object.
16
- * @return string|bool False if we're skipping.
17
- */
18
- function wpjm_yoast_skip_filled_job_listings( $url, $type, $post ) {
19
- if ( 'job_listing' !== $post->post_type ) {
20
- return $url;
21
- }
22
-
23
- if ( is_position_filled( $post ) ) {
24
- return false;
25
- }
26
-
27
- return $url;
28
- }
29
- add_action( 'wpseo_sitemap_entry', 'wpjm_yoast_skip_filled_job_listings', 10, 3 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/abstracts/abstract-wp-job-manager-email-template.php DELETED
@@ -1,133 +0,0 @@
1
- <?php
2
- /**
3
- * Abstract class for an email notification built using templates.
4
- *
5
- * @package wp-job-manager
6
- *
7
- * @since 1.31.0
8
- */
9
-
10
- if ( ! defined( 'ABSPATH' ) ) {
11
- exit; // Exit if accessed directly.
12
- }
13
-
14
- /**
15
- * Class WP_Job_Manager_Email_Template
16
- */
17
- abstract class WP_Job_Manager_Email_Template extends WP_Job_Manager_Email {
18
- /**
19
- * Get the template path for overriding templates.
20
- *
21
- * @type abstract
22
- * @return string
23
- */
24
- public static function get_template_path() {
25
- return 'job_manager';
26
- }
27
-
28
- /**
29
- * Get the default template path that WP Job Manager should look for the templates.
30
- *
31
- * @type abstract
32
- * @return string
33
- */
34
- public static function get_template_default_path() {
35
- return '';
36
- }
37
-
38
- /**
39
- * Get the rich text version of the email content.
40
- *
41
- * @return string
42
- */
43
- public function get_rich_content() {
44
- return $this->get_template( false );
45
- }
46
-
47
- /**
48
- * Get the plaintext version of the email content.
49
- *
50
- * @return string
51
- */
52
- public function get_plain_content() {
53
- if ( $this->has_template( true ) ) {
54
- return $this->get_template( true );
55
- }
56
- return parent::get_plain_content();
57
- }
58
-
59
- /**
60
- * Get the contents of a template.
61
- *
62
- * @param bool $plain_text
63
- * @return string
64
- */
65
- public function get_template( $plain_text = false ) {
66
- $template_file = $this->locate_template( $plain_text );
67
- if ( ! $template_file ) {
68
- return '';
69
- }
70
- $args = $this->get_args();
71
- $email = $this;
72
-
73
- ob_start();
74
- include $template_file;
75
- return ob_get_clean();
76
- }
77
-
78
- /**
79
- * Check to see if a template exists for this email.
80
- *
81
- * @param bool $plain_text
82
- * @return bool
83
- */
84
- public function has_template( $plain_text = false ) {
85
- $template_file = $this->locate_template( $plain_text );
86
- return $template_file && file_exists( $template_file );
87
- }
88
-
89
- /**
90
- * Locate template for this email.
91
- *
92
- * @param bool $plain_text
93
- * @return string
94
- */
95
- protected function locate_template( $plain_text ) {
96
- $class_name = get_class( $this );
97
- $template_path = call_user_func( array( $class_name, 'get_template_path' ) );
98
- $template_default_path = call_user_func( array( $class_name, 'get_template_default_path' ) );
99
- return locate_job_manager_template( $this->get_template_file_name( $plain_text ), $template_path, $template_default_path );
100
- }
101
-
102
- /**
103
- * Generate the file name for the email template.
104
- *
105
- * @param bool $plain_text
106
- * @return string
107
- */
108
- protected function get_template_file_name( $plain_text = false ) {
109
- $class_name = get_class( $this );
110
- // PHP 5.2: Using `call_user_func()` but `$class_name::get_key()` preferred.
111
- $email_notification_key = call_user_func( array( $class_name, 'get_key' ) );
112
- $template_name = str_replace( '_', '-', $email_notification_key );
113
- return self::generate_template_file_name( $template_name, $plain_text );
114
- }
115
-
116
- /**
117
- * Generate the file name for the email template.
118
- *
119
- * @param string $template_name
120
- * @param bool $plain_text
121
- * @return string
122
- */
123
- public static function generate_template_file_name( $template_name, $plain_text = false ) {
124
- $file_name_parts = array( 'emails' );
125
- if ( $plain_text ) {
126
- $file_name_parts[] = 'plain';
127
- }
128
-
129
- $file_name_parts[] = $template_name . '.php';
130
-
131
- return implode( '/', $file_name_parts );
132
- }
133
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/abstracts/abstract-wp-job-manager-email.php DELETED
@@ -1,231 +0,0 @@
1
- <?php
2
- /**
3
- * Abstract email notification class.
4
- *
5
- * @package wp-job-manager
6
- */
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit; // Exit if accessed directly.
10
- }
11
-
12
- /**
13
- * Abstract class for an email notification.
14
- *
15
- * Do not rely on WordPress global variables or functions that rely on global variables such as `wp_get_current_user()`.
16
- * Email might be generated when no longer in scope. Instead, pass the values on as an argument when initiating the email
17
- * notification.
18
- *
19
- * Additionally, inside of plugins and themes, load email notification files based on this class inside the
20
- * `job_manager_email_init` hook. This will prevent unnecessary loading and won't include the files if this abstract
21
- * class isn't available.
22
- *
23
- * Example:
24
- * ```
25
- * add_action( 'job_manager_email_init', 'custom_plugin_include_emails' );
26
- * function custom_plugin_include_emails() {
27
- * include_once 'emails/custom-plugin-sent-resume.php`;
28
- * }
29
- * ```
30
- *
31
- * @package wp-job-manager
32
- *
33
- * @since 1.31.0
34
- */
35
- abstract class WP_Job_Manager_Email {
36
- /**
37
- * Arguments used in an instance of an email notification.
38
- *
39
- * @var array
40
- */
41
- private $args = array();
42
-
43
- /**
44
- * Settings for this email notification.
45
- *
46
- * @var array
47
- */
48
- private $settings = array();
49
-
50
- /**
51
- * WP_Job_Manager_Email constructor.
52
- *
53
- * @param array $args Arguments used in forming email notification.
54
- * @param array $settings Settings for this notification.
55
- */
56
- final public function __construct( $args, $settings ) {
57
- $this->args = $this->prepare_args( (array) $args );
58
- $this->settings = (array) $settings;
59
- }
60
-
61
- /**
62
- * Get the unique email notification key.
63
- *
64
- * @type abstract
65
- *
66
- * @return string
67
- */
68
- public static function get_key() {
69
- return false;
70
- }
71
-
72
- /**
73
- * Get the friendly name for this email notification.
74
- *
75
- * @type abstract
76
- * @return string
77
- */
78
- public static function get_name() {
79
- return false;
80
- }
81
-
82
- /**
83
- * Get the description for this email notification.
84
- *
85
- * @type abstract
86
- * @return string
87
- */
88
- public static function get_description() {
89
- return '';
90
- }
91
-
92
- /**
93
- * Get the context for where this email notification is used. Used to direct which admin settings to show.
94
- *
95
- * @type abstract
96
- * @return string
97
- */
98
- public static function get_context() {
99
- return 'job_manager';
100
- }
101
-
102
- /**
103
- * Get the email subject.
104
- *
105
- * @return string
106
- */
107
- abstract public function get_subject();
108
-
109
- /**
110
- * Get `From:` address header value. Can be simple email or formatted `Firstname Lastname <email@example.com>`.
111
- *
112
- * @return string|bool Email from value or false to use WordPress' default.
113
- */
114
- abstract public function get_from();
115
-
116
- /**
117
- * Get array or comma-separated list of email addresses to send message.
118
- *
119
- * @return string|array
120
- */
121
- abstract public function get_to();
122
-
123
- /**
124
- * Get the rich text version of the email content.
125
- *
126
- * @return string
127
- */
128
- abstract public function get_rich_content();
129
-
130
- /**
131
- * Expand arguments as necessary for the generation of the email.
132
- *
133
- * @param array $args Arguments used to generate the email.
134
- * @return array
135
- */
136
- protected function prepare_args( $args ) {
137
- if ( isset( $args['job_id'] ) ) {
138
- $job = get_post( $args['job_id'] );
139
- if ( $job instanceof WP_Post ) {
140
- $args['job'] = $job;
141
- }
142
- }
143
- if ( isset( $args['job'] ) && $args['job'] instanceof WP_Post ) {
144
- $author = get_user_by( 'ID', $args['job']->post_author );
145
- if ( $author instanceof WP_User ) {
146
- $args['author'] = $author;
147
- }
148
- }
149
-
150
- return $args;
151
- }
152
-
153
- /**
154
- * Checks the arguments and returns whether the email notification is properly set up.
155
- *
156
- * @return bool
157
- */
158
- abstract public function is_valid();
159
-
160
- /**
161
- * Returns the list of file paths to attach to an email.
162
- *
163
- * @return array
164
- */
165
- public function get_attachments() {
166
- return array();
167
- }
168
-
169
- /**
170
- * Returns the value of the CC header, if needed.
171
- *
172
- * @return string|null
173
- */
174
- public function get_cc() {
175
- return null;
176
- }
177
-
178
- /**
179
- * Get the base headers for the email. No need to add CC or From headers. Content-type is added when sending rich-text.
180
- *
181
- * @return array
182
- */
183
- public function get_headers() {
184
- return array();
185
- }
186
-
187
- /**
188
- * Get the plaintext version of the email content.
189
- *
190
- * @return string
191
- */
192
- public function get_plain_content() {
193
- return strip_tags( $this->get_rich_content() );
194
- }
195
-
196
- /**
197
- * Get the settings for this email notifications.
198
- *
199
- * @return array
200
- */
201
- public static function get_setting_fields() {
202
- return array();
203
- }
204
-
205
- /**
206
- * Is this email notification enabled by default?
207
- *
208
- * @return bool
209
- */
210
- public static function is_default_enabled() {
211
- return true;
212
- }
213
-
214
- /**
215
- * Returns the args that the email notification was sent with.
216
- *
217
- * @return array
218
- */
219
- final protected function get_args() {
220
- return $this->args;
221
- }
222
-
223
- /**
224
- * Returns the settings values.
225
- *
226
- * @return array
227
- */
228
- final protected function get_settings() {
229
- return $this->settings;
230
- }
231
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/abstracts/abstract-wp-job-manager-form.php CHANGED
@@ -1,61 +1,18 @@
1
  <?php
2
 
3
  /**
4
- * Parent abstract class for form classes.
5
  *
6
  * @abstract
7
- * @package wp-job-manager
8
- * @since 1.0.0
9
  */
10
  abstract class WP_Job_Manager_Form {
11
 
12
- /**
13
- * Form fields.
14
- *
15
- * @access protected
16
- * @var array
17
- */
18
- protected $fields = array();
19
-
20
- /**
21
- * Form action.
22
- *
23
- * @access protected
24
- * @var string
25
- */
26
- protected $action = '';
27
-
28
- /**
29
- * Form errors.
30
- *
31
- * @access protected
32
- * @var array
33
- */
34
- protected $errors = array();
35
-
36
- /**
37
- * Form steps.
38
- *
39
- * @access protected
40
- * @var array
41
- */
42
- protected $steps = array();
43
-
44
- /**
45
- * Current form step.
46
- *
47
- * @access protected
48
- * @var int
49
- */
50
- protected $step = 0;
51
-
52
- /**
53
- * Form name.
54
- *
55
- * @access protected
56
- * @var string
57
- */
58
- public $form_name = '';
59
 
60
  /**
61
  * Cloning is forbidden.
@@ -65,30 +22,16 @@ abstract class WP_Job_Manager_Form {
65
  }
66
 
67
  /**
68
- * Unserializes instances of this class is forbidden.
69
  */
70
  public function __wakeup() {
71
  _doing_it_wrong( __FUNCTION__ );
72
  }
73
 
74
  /**
75
- * Processes the form result and can also change view if step is complete.
76
  */
77
  public function process() {
78
-
79
- // reset cookie.
80
- if (
81
- isset( $_GET['new'] ) &&
82
- isset( $_COOKIE['wp-job-manager-submitting-job-id'] ) &&
83
- isset( $_COOKIE['wp-job-manager-submitting-job-key'] ) &&
84
- get_post_meta( $_COOKIE['wp-job-manager-submitting-job-id'], '_submitting_key', true ) === $_COOKIE['wp-job-manager-submitting-job-key']
85
- ) {
86
- delete_post_meta( $_COOKIE['wp-job-manager-submitting-job-id'], '_submitting_key' );
87
- setcookie( 'wp-job-manager-submitting-job-id', '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN, false );
88
- setcookie( 'wp-job-manager-submitting-job-key', '', time() - 3600, COOKIEPATH, COOKIE_DOMAIN, false );
89
- wp_redirect( remove_query_arg( array( 'new', 'key' ), $_SERVER['REQUEST_URI'] ) );
90
- }
91
-
92
  $step_key = $this->get_step_key( $this->step );
93
 
94
  if ( $step_key && is_callable( $this->steps[ $step_key ]['handler'] ) ) {
@@ -104,12 +47,9 @@ abstract class WP_Job_Manager_Form {
104
  }
105
 
106
  /**
107
- * Calls the view handler if set, otherwise call the next handler.
108
- *
109
- * @param array $atts Attributes to use in the view handler.
110
  */
111
  public function output( $atts = array() ) {
112
- $this->enqueue_scripts();
113
  $step_key = $this->get_step_key( $this->step );
114
  $this->show_errors();
115
 
@@ -119,25 +59,24 @@ abstract class WP_Job_Manager_Form {
119
  }
120
 
121
  /**
122
- * Adds an error.
123
- *
124
- * @param string $error The error message.
125
  */
126
  public function add_error( $error ) {
127
  $this->errors[] = $error;
128
  }
129
 
130
  /**
131
- * Displays errors.
132
  */
133
  public function show_errors() {
134
  foreach ( $this->errors as $error ) {
135
- echo '<div class="job-manager-error">' . wp_kses_post( $error ) . '</div>';
136
  }
137
  }
138
 
139
  /**
140
- * Gets the action (URL for forms to post to).
141
  * As of 1.22.2 this defaults to the current page permalink.
142
  *
143
  * @return string
@@ -147,8 +86,7 @@ abstract class WP_Job_Manager_Form {
147
  }
148
 
149
  /**
150
- * Gets form name.
151
- *
152
  * @since 1.24.0
153
  * @return string
154
  */
@@ -157,8 +95,7 @@ abstract class WP_Job_Manager_Form {
157
  }
158
 
159
  /**
160
- * Gets steps from outside of the class.
161
- *
162
  * @since 1.24.0
163
  */
164
  public function get_steps() {
@@ -166,18 +103,15 @@ abstract class WP_Job_Manager_Form {
166
  }
167
 
168
  /**
169
- * Gets step from outside of the class.
170
  */
171
  public function get_step() {
172
  return $this->step;
173
  }
174
 
175
  /**
176
- * Gets step key from outside of the class.
177
- *
178
  * @since 1.24.0
179
- * @param string|int $step
180
- * @return string
181
  */
182
  public function get_step_key( $step = '' ) {
183
  if ( ! $step ) {
@@ -188,31 +122,29 @@ abstract class WP_Job_Manager_Form {
188
  }
189
 
190
  /**
191
- * Sets step from outside of the class.
192
- *
193
  * @since 1.24.0
194
- * @param int $step
195
  */
196
  public function set_step( $step ) {
197
  $this->step = absint( $step );
198
  }
199
 
200
  /**
201
- * Increases step from outside of the class.
202
  */
203
  public function next_step() {
204
  $this->step ++;
205
  }
206
 
207
  /**
208
- * Decreases step from outside of the class.
209
  */
210
  public function previous_step() {
211
  $this->step --;
212
  }
213
 
214
  /**
215
- * Gets fields for form.
216
  *
217
  * @param string $key
218
  * @return array
@@ -230,125 +162,29 @@ abstract class WP_Job_Manager_Form {
230
  }
231
 
232
  /**
233
- * Sorts array by priority value.
234
- *
235
  * @param array $a
236
  * @param array $b
237
  * @return int
238
  */
239
  protected function sort_by_priority( $a, $b ) {
240
- if ( intval( $a['priority'] ) === intval( $b['priority'] ) ) {
241
- return 0;
242
- }
243
- return ( intval( $a['priority'] ) < intval( $b['priority'] ) ) ? -1 : 1;
244
  }
245
 
246
  /**
247
- * Initializes form fields.
248
  */
249
  protected function init_fields() {
250
  $this->fields = array();
251
  }
252
 
253
  /**
254
- * Enqueue the scripts for the form.
255
- */
256
- public function enqueue_scripts() {
257
- if ( $this->use_recaptcha_field() ) {
258
- wp_enqueue_script( 'recaptcha', 'https://www.google.com/recaptcha/api.js' );
259
- }
260
- }
261
-
262
- /**
263
- * Checks whether reCAPTCHA has been set up and is available.
264
- *
265
- * @return bool
266
- */
267
- public function is_recaptcha_available() {
268
- $site_key = get_option( 'job_manager_recaptcha_site_key' );
269
- $secret_key = get_option( 'job_manager_recaptcha_secret_key' );
270
- $is_recaptcha_available = ! empty( $site_key ) && ! empty( $secret_key );
271
-
272
- /**
273
- * Filter whether reCAPTCHA should be available for this form.
274
- *
275
- * @since 1.30.0
276
- *
277
- * @param bool $is_recaptcha_available
278
- */
279
- return apply_filters( 'job_manager_is_recaptcha_available', $is_recaptcha_available );
280
- }
281
-
282
- /**
283
- * Show reCAPTCHA field on the form.
284
- *
285
- * @return bool
286
- */
287
- public function use_recaptcha_field() {
288
- return false;
289
- }
290
-
291
- /**
292
- * Output the reCAPTCHA field.
293
- */
294
- public function display_recaptcha_field() {
295
- $field = array();
296
- $field['label'] = get_option( 'job_manager_recaptcha_label' );
297
- $field['required'] = true;
298
- $field['site_key'] = get_option( 'job_manager_recaptcha_site_key' );
299
- get_job_manager_template(
300
- 'form-fields/recaptcha-field.php',
301
- array(
302
- 'key' => 'recaptcha',
303
- 'field' => $field,
304
- )
305
- );
306
- }
307
-
308
- /**
309
- * Validate a reCAPTCHA field.
310
  *
311
- * @param bool $success
312
- *
313
- * @return bool|WP_Error
314
- */
315
- public function validate_recaptcha_field( $success ) {
316
- $recaptcha_field_label = get_option( 'job_manager_recaptcha_label' );
317
- if ( empty( $_POST['g-recaptcha-response'] ) ) {
318
- // translators: Placeholder is for the label of the reCAPTCHA field.
319
- return new WP_Error( 'validation-error', sprintf( esc_html__( '"%s" check failed. Please try again.', 'wp-job-manager' ), $recaptcha_field_label ) );
320
- }
321
-
322
- $response = wp_remote_get(
323
- add_query_arg(
324
- array(
325
- 'secret' => get_option( 'job_manager_recaptcha_secret_key' ),
326
- 'response' => isset( $_POST['g-recaptcha-response'] ) ? $_POST['g-recaptcha-response'] : '',
327
- 'remoteip' => isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'],
328
- ),
329
- 'https://www.google.com/recaptcha/api/siteverify'
330
- )
331
- );
332
-
333
- // translators: %s is the name of the form validation that failed.
334
- $validation_error = new WP_Error( 'validation-error', sprintf( esc_html__( '"%s" check failed. Please try again.', 'wp-job-manager' ), $recaptcha_field_label ) );
335
-
336
- if ( is_wp_error( $response ) || empty( $response['body'] ) ) {
337
- return $validation_error;
338
- }
339
-
340
- $json = json_decode( $response['body'] );
341
- if ( ! $json || ! $json->success ) {
342
- return $validation_error;
343
- }
344
-
345
- return $success;
346
- }
347
-
348
- /**
349
- * Gets post data for fields.
350
- *
351
- * @return array of data.
352
  */
353
  protected function get_posted_fields() {
354
  $this->init_fields();
@@ -357,11 +193,10 @@ abstract class WP_Job_Manager_Form {
357
 
358
  foreach ( $this->fields as $group_key => $group_fields ) {
359
  foreach ( $group_fields as $key => $field ) {
360
- // Get the value.
361
  $field_type = str_replace( '-', '_', $field['type'] );
362
- $handler = apply_filters( "job_manager_get_posted_{$field_type}_field", false );
363
 
364
- if ( $handler ) {
365
  $values[ $group_key ][ $key ] = call_user_func( $handler, $key, $field );
366
  } elseif ( method_exists( $this, "get_posted_{$field_type}_field" ) ) {
367
  $values[ $group_key ][ $key ] = call_user_func( array( $this, "get_posted_{$field_type}_field" ), $key, $field );
@@ -369,7 +204,7 @@ abstract class WP_Job_Manager_Form {
369
  $values[ $group_key ][ $key ] = $this->get_posted_field( $key, $field );
370
  }
371
 
372
- // Set fields value.
373
  $this->fields[ $group_key ][ $key ]['value'] = $values[ $group_key ][ $key ];
374
  }
375
  }
@@ -380,66 +215,35 @@ abstract class WP_Job_Manager_Form {
380
  /**
381
  * Navigates through an array and sanitizes the field.
382
  *
383
- * @since 1.22.0
384
- * @since 1.29.1 Added the $sanitizer argument
385
- *
386
- * @param array|string $value The array or string to be sanitized.
387
- * @param string|callable $sanitizer The sanitization method to use. Built in: `url`, `email`, `url_or_email`, or
388
- * default (text). Custom single argument callable allowed.
389
- * @return array|string $value The sanitized array (or string from the callback).
390
  */
391
- protected function sanitize_posted_field( $value, $sanitizer = null ) {
392
- // Sanitize value.
393
- if ( is_array( $value ) ) {
394
- foreach ( $value as $key => $val ) {
395
- $value[ $key ] = $this->sanitize_posted_field( $val, $sanitizer );
396
- }
397
-
398
- return $value;
399
  }
400
 
401
- $value = trim( $value );
402
-
403
- if ( 'url' === $sanitizer ) {
404
- return esc_url_raw( $value );
405
- } elseif ( 'email' === $sanitizer ) {
406
- return sanitize_email( $value );
407
- } elseif ( 'url_or_email' === $sanitizer ) {
408
- if ( null !== wp_parse_url( $value, PHP_URL_HOST ) ) {
409
- // Sanitize as URL.
410
- return esc_url_raw( $value );
411
- }
412
 
413
- // Sanitize as email.
414
- return sanitize_email( $value );
415
- } elseif ( is_callable( $sanitizer ) ) {
416
- return call_user_func( $sanitizer, $value );
417
- }
418
-
419
- // Use standard text sanitizer.
420
- return sanitize_text_field( stripslashes( $value ) );
421
  }
422
 
423
  /**
424
- * Gets the value of a posted field.
425
- *
426
  * @param string $key
427
- * @param array $field
428
  * @return string|array
429
  */
430
  protected function get_posted_field( $key, $field ) {
431
- // Allow custom sanitizers with standard text fields.
432
- if ( ! isset( $field['sanitizer'] ) ) {
433
- $field['sanitizer'] = null;
434
- }
435
- return isset( $_POST[ $key ] ) ? $this->sanitize_posted_field( $_POST[ $key ], $field['sanitizer'] ) : '';
436
  }
437
 
438
  /**
439
- * Gets the value of a posted multiselect field.
440
- *
441
  * @param string $key
442
- * @param array $field
443
  * @return array
444
  */
445
  protected function get_posted_multiselect_field( $key, $field ) {
@@ -447,13 +251,10 @@ abstract class WP_Job_Manager_Form {
447
  }
448
 
449
  /**
450
- * Gets the value of a posted file field.
451
- *
452
  * @param string $key
453
- * @param array $field
454
- *
455
  * @return string|array
456
- * @throws Exception When the upload fails.
457
  */
458
  protected function get_posted_file_field( $key, $field ) {
459
  $file = $this->upload_file( $key, $field );
@@ -468,10 +269,9 @@ abstract class WP_Job_Manager_Form {
468
  }
469
 
470
  /**
471
- * Gets the value of a posted textarea field.
472
- *
473
  * @param string $key
474
- * @param array $field
475
  * @return string
476
  */
477
  protected function get_posted_textarea_field( $key, $field ) {
@@ -479,10 +279,9 @@ abstract class WP_Job_Manager_Form {
479
  }
480
 
481
  /**
482
- * Gets the value of a posted textarea field.
483
- *
484
  * @param string $key
485
- * @param array $field
486
  * @return string
487
  */
488
  protected function get_posted_wp_editor_field( $key, $field ) {
@@ -490,36 +289,33 @@ abstract class WP_Job_Manager_Form {
490
  }
491
 
492
  /**
493
- * Gets posted terms for the taxonomy.
494
- *
495
  * @param string $key
496
- * @param array $field
497
  * @return array
498
  */
499
  protected function get_posted_term_checklist_field( $key, $field ) {
500
- if ( isset( $_POST['tax_input'] ) && isset( $_POST['tax_input'][ $field['taxonomy'] ] ) ) {
501
- return array_map( 'absint', $_POST['tax_input'][ $field['taxonomy'] ] );
502
  } else {
503
  return array();
504
  }
505
  }
506
 
507
  /**
508
- * Gets posted terms for the taxonomy.
509
- *
510
  * @param string $key
511
- * @param array $field
512
- * @return array
513
  */
514
  protected function get_posted_term_multiselect_field( $key, $field ) {
515
  return isset( $_POST[ $key ] ) ? array_map( 'absint', $_POST[ $key ] ) : array();
516
  }
517
 
518
  /**
519
- * Gets posted terms for the taxonomy.
520
- *
521
  * @param string $key
522
- * @param array $field
523
  * @return int
524
  */
525
  protected function get_posted_term_select_field( $key, $field ) {
@@ -527,32 +323,22 @@ abstract class WP_Job_Manager_Form {
527
  }
528
 
529
  /**
530
- * Handles the uploading of files.
531
- *
532
- * @param string $field_key
533
- * @param array $field
534
- * @throws Exception When file upload failed.
535
- * @return string|array
536
  */
537
  protected function upload_file( $field_key, $field ) {
538
  if ( isset( $_FILES[ $field_key ] ) && ! empty( $_FILES[ $field_key ] ) && ! empty( $_FILES[ $field_key ]['name'] ) ) {
539
  if ( ! empty( $field['allowed_mime_types'] ) ) {
540
  $allowed_mime_types = $field['allowed_mime_types'];
541
  } else {
542
- $allowed_mime_types = job_manager_get_allowed_mime_types();
543
  }
544
 
545
  $file_urls = array();
546
  $files_to_upload = job_manager_prepare_uploaded_files( $_FILES[ $field_key ] );
547
 
548
  foreach ( $files_to_upload as $file_to_upload ) {
549
- $uploaded_file = job_manager_upload_file(
550
- $file_to_upload,
551
- array(
552
- 'file_key' => $field_key,
553
- 'allowed_mime_types' => $allowed_mime_types,
554
- )
555
- );
556
 
557
  if ( is_wp_error( $uploaded_file ) ) {
558
  throw new Exception( $uploaded_file->get_error_message() );
1
  <?php
2
 
3
  /**
4
+ * Abstract WP_Job_Manager_Form class.
5
  *
6
  * @abstract
 
 
7
  */
8
  abstract class WP_Job_Manager_Form {
9
 
10
+ protected $fields = array();
11
+ protected $action = '';
12
+ protected $errors = array();
13
+ protected $steps = array();
14
+ protected $step = 0;
15
+ public $form_name = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  /**
18
  * Cloning is forbidden.
22
  }
23
 
24
  /**
25
+ * Unserializing instances of this class is forbidden.
26
  */
27
  public function __wakeup() {
28
  _doing_it_wrong( __FUNCTION__ );
29
  }
30
 
31
  /**
32
+ * Process function. all processing code if needed - can also change view if step is complete
33
  */
34
  public function process() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  $step_key = $this->get_step_key( $this->step );
36
 
37
  if ( $step_key && is_callable( $this->steps[ $step_key ]['handler'] ) ) {
47
  }
48
 
49
  /**
50
+ * Call the view handler if set, otherwise call the next handler.
 
 
51
  */
52
  public function output( $atts = array() ) {
 
53
  $step_key = $this->get_step_key( $this->step );
54
  $this->show_errors();
55
 
59
  }
60
 
61
  /**
62
+ * Add an error
63
+ * @param string $error
 
64
  */
65
  public function add_error( $error ) {
66
  $this->errors[] = $error;
67
  }
68
 
69
  /**
70
+ * Show errors
71
  */
72
  public function show_errors() {
73
  foreach ( $this->errors as $error ) {
74
+ echo '<div class="job-manager-error">' . $error . '</div>';
75
  }
76
  }
77
 
78
  /**
79
+ * Get action (URL for forms to post to).
80
  * As of 1.22.2 this defaults to the current page permalink.
81
  *
82
  * @return string
86
  }
87
 
88
  /**
89
+ * Get formn name.
 
90
  * @since 1.24.0
91
  * @return string
92
  */
95
  }
96
 
97
  /**
98
+ * Get steps from outside of the class
 
99
  * @since 1.24.0
100
  */
101
  public function get_steps() {
103
  }
104
 
105
  /**
106
+ * Get step from outside of the class
107
  */
108
  public function get_step() {
109
  return $this->step;
110
  }
111
 
112
  /**
113
+ * Get step key from outside of the class
 
114
  * @since 1.24.0
 
 
115
  */
116
  public function get_step_key( $step = '' ) {
117
  if ( ! $step ) {
122
  }
123
 
124
  /**
125
+ * Get step from outside of the class
 
126
  * @since 1.24.0
 
127
  */
128
  public function set_step( $step ) {
129
  $this->step = absint( $step );
130
  }
131
 
132
  /**
133
+ * Increase step from outside of the class
134
  */
135
  public function next_step() {
136
  $this->step ++;
137
  }
138
 
139
  /**
140
+ * Decrease step from outside of the class
141
  */
142
  public function previous_step() {
143
  $this->step --;
144
  }
145
 
146
  /**
147
+ * get_fields function.
148
  *
149
  * @param string $key
150
  * @return array
162
  }
163
 
164
  /**
165
+ * Sort array by priority value
 
166
  * @param array $a
167
  * @param array $b
168
  * @return int
169
  */
170
  protected function sort_by_priority( $a, $b ) {
171
+ if ( $a['priority'] == $b['priority'] ) {
172
+ return 0;
173
+ }
174
+ return ( $a['priority'] < $b['priority'] ) ? -1 : 1;
175
  }
176
 
177
  /**
178
+ * Init form fields
179
  */
180
  protected function init_fields() {
181
  $this->fields = array();
182
  }
183
 
184
  /**
185
+ * Get post data for fields
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  *
187
+ * @return array of data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  */
189
  protected function get_posted_fields() {
190
  $this->init_fields();
193
 
194
  foreach ( $this->fields as $group_key => $group_fields ) {
195
  foreach ( $group_fields as $key => $field ) {
196
+ // Get the value
197
  $field_type = str_replace( '-', '_', $field['type'] );
 
198
 
199
+ if ( $handler = apply_filters( "job_manager_get_posted_{$field_type}_field", false ) ) {
200
  $values[ $group_key ][ $key ] = call_user_func( $handler, $key, $field );
201
  } elseif ( method_exists( $this, "get_posted_{$field_type}_field" ) ) {
202
  $values[ $group_key ][ $key ] = call_user_func( array( $this, "get_posted_{$field_type}_field" ), $key, $field );
204
  $values[ $group_key ][ $key ] = $this->get_posted_field( $key, $field );
205
  }
206
 
207
+ // Set fields value
208
  $this->fields[ $group_key ][ $key ]['value'] = $values[ $group_key ][ $key ];
209
  }
210
  }
215
  /**
216
  * Navigates through an array and sanitizes the field.
217
  *
218
+ * @param array|string $value The array or string to be sanitized.
219
+ * @return array|string $value The sanitized array (or string from the callback).
 
 
 
 
 
220
  */
221
+ protected function sanitize_posted_field( $value ) {
222
+ // Decode URLs
223
+ if ( is_string( $value ) && ( strstr( $value, 'http:' ) || strstr( $value, 'https:' ) ) ) {
224
+ $value = urldecode( $value );
 
 
 
 
225
  }
226
 
227
+ // Santize value
228
+ $value = is_array( $value ) ? array_map( array( $this, 'sanitize_posted_field' ), $value ) : sanitize_text_field( stripslashes( trim( $value ) ) );
 
 
 
 
 
 
 
 
 
229
 
230
+ return $value;
 
 
 
 
 
 
 
231
  }
232
 
233
  /**
234
+ * Get the value of a posted field
 
235
  * @param string $key
236
+ * @param array $field
237
  * @return string|array
238
  */
239
  protected function get_posted_field( $key, $field ) {
240
+ return isset( $_POST[ $key ] ) ? $this->sanitize_posted_field( $_POST[ $key ] ) : '';
 
 
 
 
241
  }
242
 
243
  /**
244
+ * Get the value of a posted multiselect field
 
245
  * @param string $key
246
+ * @param array $field
247
  * @return array
248
  */
249
  protected function get_posted_multiselect_field( $key, $field ) {
251
  }
252
 
253
  /**
254
+ * Get the value of a posted file field
 
255
  * @param string $key
256
+ * @param array $field
 
257
  * @return string|array
 
258
  */
259
  protected function get_posted_file_field( $key, $field ) {
260
  $file = $this->upload_file( $key, $field );
269
  }
270
 
271
  /**
272
+ * Get the value of a posted textarea field
 
273
  * @param string $key
274
+ * @param array $field
275
  * @return string
276
  */
277
  protected function get_posted_textarea_field( $key, $field ) {
279
  }
280
 
281
  /**
282
+ * Get the value of a posted textarea field
 
283
  * @param string $key
284
+ * @param array $field
285
  * @return string
286
  */
287
  protected function get_posted_wp_editor_field( $key, $field ) {
289
  }
290
 
291
  /**
292
+ * Get posted terms for the taxonomy
 
293
  * @param string $key
294
+ * @param array $field
295
  * @return array
296
  */
297
  protected function get_posted_term_checklist_field( $key, $field ) {
298
+ if ( isset( $_POST[ 'tax_input' ] ) && isset( $_POST[ 'tax_input' ][ $field['taxonomy'] ] ) ) {
299
+ return array_map( 'absint', $_POST[ 'tax_input' ][ $field['taxonomy'] ] );
300
  } else {
301
  return array();
302
  }
303
  }
304
 
305
  /**
306
+ * Get posted terms for the taxonomy
 
307
  * @param string $key
308
+ * @param array $field
309
+ * @return int
310
  */
311
  protected function get_posted_term_multiselect_field( $key, $field ) {
312
  return isset( $_POST[ $key ] ) ? array_map( 'absint', $_POST[ $key ] ) : array();
313
  }
314
 
315
  /**
316
+ * Get posted terms for the taxonomy
 
317
  * @param string $key
318
+ * @param array $field
319
  * @return int
320
  */
321
  protected function get_posted_term_select_field( $key, $field ) {
323
  }
324
 
325
  /**
326
+ * Upload a file
327
+ * @return string or array
 
 
 
 
328
  */
329
  protected function upload_file( $field_key, $field ) {
330
  if ( isset( $_FILES[ $field_key ] ) && ! empty( $_FILES[ $field_key ] ) && ! empty( $_FILES[ $field_key ]['name'] ) ) {
331
  if ( ! empty( $field['allowed_mime_types'] ) ) {
332
  $allowed_mime_types = $field['allowed_mime_types'];
333
  } else {
334
+ $allowed_mime_types = get_allowed_mime_types();
335
  }
336
 
337
  $file_urls = array();
338
  $files_to_upload = job_manager_prepare_uploaded_files( $_FILES[ $field_key ] );
339
 
340
  foreach ( $files_to_upload as $file_to_upload ) {
341
+ $uploaded_file = job_manager_upload_file( $file_to_upload, array( 'file_key' => $field_key ) );
 
 
 
 
 
 
342
 
343
  if ( is_wp_error( $uploaded_file ) ) {
344
  throw new Exception( $uploaded_file->get_error_message() );
includes/admin/class-wp-job-manager-addons.php CHANGED
@@ -1,150 +1,69 @@
1
  <?php
2
  /**
3
- * Addons Page.
4
- *
5
- * @package wp-job-manager
6
  */
7
 
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit; // Exit if accessed directly.
10
- }
11
 
12
  /**
13
- * Handles the admin add-ons page.
14
- *
15
- * @package wp-job-manager
16
- * @since 1.1.0
17
  */
18
  class WP_Job_Manager_Addons {
19
- const WPJM_COM_PRODUCTS_API_BASE_URL = 'https://wpjobmanager.com/wp-json/wpjmcom-products/1.0';
20
 
21
  /**
22
- * The single instance of the class.
23
- *
24
- * @var self
25
- * @since 1.26.0
26
  */
27
- private static $_instance = null;
28
 
29
- /**
30
- * Allows for accessing single instance of class. Class should only be constructed once per call.
31
- *
32
- * @since 1.26.0
33
- * @static
34
- * @return self Main instance.
35
- */
36
- public static function instance() {
37
- if ( is_null( self::$_instance ) ) {
38
- self::$_instance = new self();
39
- }
40
- return self::$_instance;
41
- }
42
 
43
- /**
44
- * Call API to get WPJM add-ons
45
- *
46
- * @since 1.30.0
47
- *
48
- * @param string $category
49
- *
50
- * @return array of add-ons.
51
- */
52
- private function get_add_ons( $category = null ) {
53
- $raw_add_ons = wp_remote_get(
54
- add_query_arg( array( array( 'category' => $category ) ), self::WPJM_COM_PRODUCTS_API_BASE_URL . '/search' )
55
- );
56
- if ( ! is_wp_error( $raw_add_ons ) ) {
57
- $add_ons = json_decode( wp_remote_retrieve_body( $raw_add_ons ) )->products;
58
- }
59
- return $add_ons;
60
- }
61
 
62
- /**
63
- * Get categories for the add-ons screen
64
- *
65
- * @since 1.30.0
66
- *
67
- * @return array of objects.
68
- */
69
- private function get_categories() {
70
- $add_on_categories = get_transient( 'jm_wpjmcom_add_on_categories' );
71
- if ( false === ( $add_on_categories ) ) {
72
- $raw_categories = wp_safe_remote_get( self::WPJM_COM_PRODUCTS_API_BASE_URL . '/categories' );
73
- if ( ! is_wp_error( $raw_categories ) ) {
74
- $add_on_categories = json_decode( wp_remote_retrieve_body( $raw_categories ) );
75
- if ( $add_on_categories ) {
76
- set_transient( 'jm_wpjmcom_add_on_categories', $add_on_categories, WEEK_IN_SECONDS );
77
  }
78
- }
79
- }
80
- return apply_filters( 'job_manager_add_on_categories', $add_on_categories );
81
- }
82
 
83
- /**
84
- * Get messages for the add-ons screen
85
- *
86
- * @since 1.30.0
87
- *
88
- * @return array of objects.
89
- */
90
- private function get_messages() {
91
- $add_on_messages = get_transient( 'jm_wpjmcom_add_on_messages' );
92
- if ( false === ( $add_on_messages ) ) {
93
- $raw_messages = wp_safe_remote_get(
94
- add_query_arg(
95
- array(
96
- 'version' => JOB_MANAGER_VERSION,
97
- 'lang' => get_locale(),
98
- ), self::WPJM_COM_PRODUCTS_API_BASE_URL . '/messages'
99
- )
100
- );
101
- if ( ! is_wp_error( $raw_messages ) ) {
102
- $add_on_messages = json_decode( wp_remote_retrieve_body( $raw_messages ) );
103
- if ( $add_on_messages ) {
104
- set_transient( 'jm_wpjmcom_add_on_messages', $add_on_messages, WEEK_IN_SECONDS );
105
  }
106
  }
107
  }
108
- return apply_filters( 'job_manager_add_on_messages', $add_on_messages );
109
- }
110
 
111
- /**
112
- * Handles output of the reports page in admin.
113
- */
114
- public function output() {
115
  ?>
116
- <div class="wrap wp_job_manager wp_job_manager_add_ons_wrap">
117
- <nav class="nav-tab-wrapper woo-nav-tab-wrapper">
118
- <a href="<?php echo esc_url( admin_url( 'edit.php?post_type=job_listing&page=job-manager-addons' ) ); ?>" class="nav-tab
119
- <?php
120
- if ( ! isset( $_GET['section'] ) || 'helper' !== $_GET['section'] ) {
121
- echo ' nav-tab-active';
122
- }
123
- ?>
124
- "><?php esc_html_e( 'WP Job Manager Add-ons', 'wp-job-manager' ); ?></a>
125
- <?php if ( current_user_can( 'update_plugins' ) ) : ?>
126
- <a href="<?php echo esc_url( admin_url( 'edit.php?post_type=job_listing&page=job-manager-addons&section=helper' ) ); ?>" class="nav-tab
127
- <?php
128
- if ( isset( $_GET['section'] ) && 'helper' === $_GET['section'] ) {
129
- echo ' nav-tab-active'; }
130
- ?>
131
- "><?php esc_html_e( 'Licenses', 'wp-job-manager' ); ?></a>
132
- <?php endif; ?>
133
- </nav>
134
- <?php
135
- if ( isset( $_GET['section'] ) && 'helper' === $_GET['section'] ) {
136
- do_action( 'job_manager_helper_output' );
137
- } else {
138
- $category = isset( $_GET['category'] ) ? sanitize_text_field( $_GET['category'] ) : null;
139
- $messages = $this->get_messages();
140
- $categories = $this->get_categories();
141
- $add_ons = $this->get_add_ons( $category );
142
- include_once dirname( __FILE__ ) . '/views/html-admin-page-addons.php';
143
- }
144
- ?>
145
  </div>
146
  <?php
147
  }
148
  }
149
 
150
- return WP_Job_Manager_Addons::instance();
 
 
1
  <?php
2
  /**
3
+ * Addons Page
 
 
4
  */
5
 
6
+ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
7
+
8
+ if ( ! class_exists( 'WP_Job_Manager_Addons' ) ) :
9
 
10
  /**
11
+ * WP_Job_Manager_Addons Class
 
 
 
12
  */
13
  class WP_Job_Manager_Addons {
 
14
 
15
  /**
16
+ * Handles output of the reports page in admin.
 
 
 
17
  */
18
+ public function output() {
19
 
20
+ if ( false === ( $addons = get_transient( 'wp_job_manager_addons_html' ) ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ $raw_addons = wp_remote_get(
23
+ 'https://wpjobmanager.com/add-ons/',
24
+ array(
25
+ 'timeout' => 10,
26
+ 'redirection' => 5,
27
+ 'sslverify' => false
28
+ )
29
+ );
 
 
 
 
 
 
 
 
 
 
30
 
31
+ if ( ! is_wp_error( $raw_addons ) ) {
32
+
33
+ $raw_addons = wp_remote_retrieve_body( $raw_addons );
34
+
35
+ // Get Products
36
+ $dom = new DOMDocument();
37
+ libxml_use_internal_errors(true);
38
+ $dom->loadHTML( $raw_addons );
39
+
40
+ $xpath = new DOMXPath( $dom );
41
+ $tags = $xpath->query('//ul[@class="products"]');
42
+ foreach ( $tags as $tag ) {
43
+ $addons = $tag->ownerDocument->saveXML( $tag );
44
+ break;
 
45
  }
 
 
 
 
46
 
47
+ $addons = wp_kses_post( $addons );
48
+
49
+ if ( $addons ) {
50
+ set_transient( 'wp_job_manager_addons_html', $addons, 60*60*24*7 ); // Cached for a week
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  }
52
  }
53
  }
 
 
54
 
 
 
 
 
55
  ?>
56
+ <div class="wrap wp_job_manager wp_job_manager_addons_wrap">
57
+ <h2><?php _e( 'WP Job Manager Add-ons', 'wp-job-manager' ); ?></h2>
58
+
59
+ <div id="job-manager-addons-banner" class="notice updated below-h2"><strong><?php _e( 'Do you need multiple add-ons?', 'wp-job-manager' ); ?></strong> <a href="https://wpjobmanager.com/add-ons/bundle/" class="button"><?php _e( 'Check out the core add-on bundle &rarr;', 'wp-job-manager' ); ?></a></div>
60
+
61
+ <?php echo $addons; ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  </div>
63
  <?php
64
  }
65
  }
66
 
67
+ endif;
68
+
69
+ return new WP_Job_Manager_Addons();
includes/admin/class-wp-job-manager-admin.php CHANGED
@@ -1,168 +1,77 @@
1
  <?php
2
 
3
- if ( ! defined( 'ABSPATH' ) ) {
4
- exit; // Exit if accessed directly.
5
- }
6
 
7
  /**
8
- * Handles front admin page for WP Job Manager.
9
- *
10
- * @package wp-job-manager
11
- * @since 1.0.0
12
  */
13
  class WP_Job_Manager_Admin {
14
 
15
  /**
16
- * The single instance of the class.
17
- *
18
- * @var self
19
- * @since 1.26.0
20
- */
21
- private static $_instance = null;
22
-
23
- /**
24
- * Allows for accessing single instance of class. Class should only be constructed once per call.
25
  *
26
- * @since 1.26.0
27
- * @static
28
- * @return self Main instance.
29
- */
30
- public static function instance() {
31
- if ( is_null( self::$_instance ) ) {
32
- self::$_instance = new self();
33
- }
34
- return self::$_instance;
35
- }
36
-
37
- /**
38
- * Constructor.
39
  */
40
  public function __construct() {
41
- global $wp_version;
 
 
 
42
 
43
- include_once dirname( __FILE__ ) . '/class-wp-job-manager-cpt.php';
44
- if ( version_compare( $wp_version, '4.7.0', '<' ) ) {
45
- include_once dirname( __FILE__ ) . '/class-wp-job-manager-cpt-legacy.php';
46
- WP_Job_Manager_CPT_Legacy::instance();
47
- } else {
48
- WP_Job_Manager_CPT::instance();
49
- }
50
- include_once dirname( __FILE__ ) . '/class-wp-job-manager-settings.php';
51
- include_once dirname( __FILE__ ) . '/class-wp-job-manager-writepanels.php';
52
- include_once dirname( __FILE__ ) . '/class-wp-job-manager-setup.php';
53
-
54
- $this->settings_page = WP_Job_Manager_Settings::instance();
55
 
56
- add_action( 'admin_init', array( $this, 'admin_init' ) );
57
- add_action( 'current_screen', array( $this, 'conditional_includes' ) );
58
  add_action( 'admin_menu', array( $this, 'admin_menu' ), 12 );
59
  add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
60
  }
61
 
62
  /**
63
- * Set up actions during admin initialization.
64
- */
65
- public function admin_init() {
66
- global $wp_version;
67
-
68
- include_once dirname( __FILE__ ) . '/class-wp-job-manager-taxonomy-meta.php';
69
-
70
- if ( version_compare( $wp_version, JOB_MANAGER_MINIMUM_WP_VERSION, '<' ) ) {
71
- add_action( 'admin_notices', array( $this, 'wp_version_admin_notice' ) );
72
- add_filter( 'plugin_action_links_' . JOB_MANAGER_PLUGIN_BASENAME, array( $this, 'wp_version_plugin_action_notice' ) );
73
- }
74
- }
75
-
76
- /**
77
- * Display notice if WordPress core is out-of-date in admin notice section.
78
- */
79
- public function wp_version_admin_notice() {
80
- // We only want to show the notices on the plugins page and WPJM admin pages.
81
- $screen = get_current_screen();
82
- $valid_screens = array( 'plugins', 'edit-job_listing', 'job_listing_page_job-manager-settings', 'edit-job_listing_type', 'edit-job_listing_category', 'job_listing' );
83
- if ( null === $screen || ! in_array( $screen->id, $valid_screens, true ) ) {
84
- return;
85
- }
86
-
87
- echo '<div class="error">';
88
- // translators: %s is the URL for the page where users can go to update WordPress.
89
- echo '<p>' . wp_kses_post( 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>';
90
- echo '</div>';
91
- }
92
-
93
- /**
94
- * Add admin notice when WP upgrade is required.
95
  *
96
- * @param array $actions
97
- * @return array
98
  */
99
- public function wp_version_plugin_action_notice( $actions ) {
100
- // translators: Placeholder (%s) is the URL where users can go to update WordPress.
101
- $actions[] = wp_kses_post( sprintf( __( '<a href="%s" style="color: red">WordPress Update Required</a>', 'wp-job-manager' ), esc_url( self_admin_url( 'update-core.php' ) ) ) );
102
- return $actions;
103
- }
104
 
105
- /**
106
- * Include admin files conditionally.
107
- */
108
- public function conditional_includes() {
109
  $screen = get_current_screen();
110
- if ( ! $screen ) {
111
- return;
112
- }
113
- switch ( $screen->id ) {
114
- case 'options-permalink':
115
- include 'class-wp-job-manager-permalink-settings.php';
116
- break;
117
- }
118
- }
119
 
120
- /**
121
- * Enqueues CSS and JS assets.
122
- */
123
- public function admin_enqueue_scripts() {
124
- $screen = get_current_screen();
125
 
126
- 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' ) ), true ) ) {
127
- wp_enqueue_style( 'jquery-ui' );
128
- wp_enqueue_style( 'job_manager_admin_css', JOB_MANAGER_PLUGIN_URL . '/assets/css/admin.css', array(), JOB_MANAGER_VERSION );
129
- wp_register_script( 'jquery-tiptip', JOB_MANAGER_PLUGIN_URL . '/assets/js/jquery-tiptip/jquery.tipTip.min.js', array( 'jquery' ), JOB_MANAGER_VERSION, true );
130
- 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 );
131
- wp_enqueue_script( 'job_manager_admin_js', JOB_MANAGER_PLUGIN_URL . '/assets/js/admin.min.js', array( 'jquery', 'jquery-tiptip' ), JOB_MANAGER_VERSION, true );
132
 
133
- if ( ! function_exists( 'wp_localize_jquery_ui_datepicker' ) || ! has_action( 'admin_enqueue_scripts', 'wp_localize_jquery_ui_datepicker' ) ) {
134
- wp_localize_script(
135
- 'job_manager_datepicker_js',
136
- 'job_manager_datepicker',
137
- array(
138
- /* translators: jQuery date format, see http://api.jqueryui.com/datepicker/#utility-formatDate */
139
- 'date_format' => _x( 'yy-mm-dd', 'Date format for jQuery datepicker.', 'wp-job-manager' ),
140
- )
141
- );
142
- }
143
  }
144
 
145
- wp_enqueue_style( 'job_manager_admin_menu_css', JOB_MANAGER_PLUGIN_URL . '/assets/css/menu.css', array(), JOB_MANAGER_VERSION );
146
  }
147
 
148
  /**
149
- * Adds pages to admin menu.
 
 
 
150
  */
151
  public function admin_menu() {
152
  add_submenu_page( 'edit.php?post_type=job_listing', __( 'Settings', 'wp-job-manager' ), __( 'Settings', 'wp-job-manager' ), 'manage_options', 'job-manager-settings', array( $this->settings_page, 'output' ) );
153
 
154
- if ( WP_Job_Manager_Helper::instance()->has_licenced_products() || apply_filters( 'job_manager_show_addons_page', true ) ) {
155
- add_submenu_page( 'edit.php?post_type=job_listing', __( 'WP Job Manager Add-ons', 'wp-job-manager' ), __( 'Add-ons', 'wp-job-manager' ), 'manage_options', 'job-manager-addons', array( $this, 'addons_page' ) );
156
- }
157
  }
158
 
159
  /**
160
- * Displays addons page.
161
  */
162
  public function addons_page() {
163
- $addons = include 'class-wp-job-manager-addons.php';
164
  $addons->output();
165
  }
166
  }
167
 
168
- WP_Job_Manager_Admin::instance();
1
  <?php
2
 
3
+ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
4
 
5
  /**
6
+ * WP_Job_Manager_Admin class.
 
 
 
7
  */
8
  class WP_Job_Manager_Admin {
9
 
10
  /**
11
+ * __construct function.
 
 
 
 
 
 
 
 
12
  *
13
+ * @access public
14
+ * @return void
 
 
 
 
 
 
 
 
 
 
 
15
  */
16
  public function __construct() {
17
+ include_once( 'class-wp-job-manager-cpt.php' );
18
+ include_once( 'class-wp-job-manager-settings.php' );
19
+ include_once( 'class-wp-job-manager-writepanels.php' );
20
+ include_once( 'class-wp-job-manager-setup.php' );
21
 
22
+ $this->settings_page = new WP_Job_Manager_Settings();
 
 
 
 
 
 
 
 
 
 
 
23
 
 
 
24
  add_action( 'admin_menu', array( $this, 'admin_menu' ), 12 );
25
  add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
26
  }
27
 
28
  /**
29
+ * admin_enqueue_scripts function.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  *
31
+ * @access public
32
+ * @return void
33
  */
34
+ public function admin_enqueue_scripts() {
35
+ global $wp_scripts;
 
 
 
36
 
 
 
 
 
37
  $screen = get_current_screen();
 
 
 
 
 
 
 
 
 
38
 
39
+ if ( in_array( $screen->id, apply_filters( 'job_manager_admin_screen_ids', array( 'edit-job_listing', 'job_listing', 'job_listing_page_job-manager-settings', 'job_listing_page_job-manager-addons' ) ) ) ) {
40
+ $jquery_version = isset( $wp_scripts->registered['jquery-ui-core']->ver ) ? $wp_scripts->registered['jquery-ui-core']->ver : '1.9.2';
 
 
 
41
 
42
+ wp_enqueue_style( 'jquery-ui-style', '//code.jquery.com/ui/' . $jquery_version . '/themes/smoothness/jquery-ui.css', array(), $jquery_version );
43
+ wp_enqueue_style( 'job_manager_admin_css', JOB_MANAGER_PLUGIN_URL . '/assets/css/admin.css' );
44
+ wp_register_script( 'jquery-tiptip', JOB_MANAGER_PLUGIN_URL. '/assets/js/jquery-tiptip/jquery.tipTip.min.js', array( 'jquery' ), JOB_MANAGER_VERSION, true );
45
+ wp_enqueue_script( 'job_manager_admin_js', JOB_MANAGER_PLUGIN_URL. '/assets/js/admin.min.js', array( 'jquery', 'jquery-tiptip', 'jquery-ui-datepicker' ), JOB_MANAGER_VERSION, true );
 
 
46
 
47
+ wp_localize_script( 'job_manager_admin_js', 'job_manager_admin', array(
48
+ 'date_format' => _x( 'yy-mm-dd', 'Date format for jQuery datepicker', 'wp-job-manager' )
49
+ ) );
 
 
 
 
 
 
 
50
  }
51
 
52
+ wp_enqueue_style( 'job_manager_admin_menu_css', JOB_MANAGER_PLUGIN_URL . '/assets/css/menu.css' );
53
  }
54
 
55
  /**
56
+ * admin_menu function.
57
+ *
58
+ * @access public
59
+ * @return void
60
  */
61
  public function admin_menu() {
62
  add_submenu_page( 'edit.php?post_type=job_listing', __( 'Settings', 'wp-job-manager' ), __( 'Settings', 'wp-job-manager' ), 'manage_options', 'job-manager-settings', array( $this->settings_page, 'output' ) );
63
 
64
+ if ( apply_filters( 'job_manager_show_addons_page', true ) )
65
+ add_submenu_page( 'edit.php?post_type=job_listing', __( 'WP Job Manager Add-ons', 'wp-job-manager' ), __( 'Add-ons', 'wp-job-manager' ) , 'manage_options', 'job-manager-addons', array( $this, 'addons_page' ) );
 
66
  }
67
 
68
  /**
69
+ * Output addons page
70
  */
71
  public function addons_page() {
72
+ $addons = include( 'class-wp-job-manager-addons.php' );
73
  $addons->output();
74
  }
75
  }
76
 
77
+ new WP_Job_Manager_Admin();
includes/admin/class-wp-job-manager-cpt-legacy.php DELETED
@@ -1,91 +0,0 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) {
4
- exit; // Exit if accessed directly.
5
- }
6
-
7
- /**
8
- * Handles legacy actions and filters specific to the custom post type for Job Listings.
9
- *
10
- * @package wp-job-manager
11
- * @since 1.27.0
12
- */
13
- class WP_Job_Manager_CPT_Legacy extends WP_Job_Manager_CPT {
14
- /**
15
- * The single instance of the class.
16
- *
17
- * @var self
18
- * @since 1.27.0
19
- */
20
- private static $_instance = null;
21
-
22
- /**
23
- * Allows for accessing single instance of class. Class should only be constructed once per call.
24
- *
25
- * @since 1.27.0
26
- * @static
27
- * @return self Main instance.
28
- */
29
- public static function instance() {
30
- if ( is_null( self::$_instance ) ) {
31
- self::$_instance = new self();
32
- }
33
-
34
- return self::$_instance;
35
- }
36
-
37
- /**
38
- * Constructor.
39
- */
40
- public function __construct() {
41
- parent::__construct();
42
- add_action( 'admin_footer-edit.php', array( $this, 'add_bulk_actions_legacy' ) );
43
- add_action( 'load-edit.php', array( $this, 'do_bulk_actions_legacy' ) );
44
- remove_action( 'bulk_actions-edit-job_listing', array( $this, 'add_bulk_actions' ) );
45
- }
46
-
47
- /**
48
- * Adds bulk actions to drop downs on Job Listing admin page.
49
- */
50
- public function add_bulk_actions_legacy() {
51
- global $post_type, $wp_post_types;
52
-
53
- $bulk_actions = array();
54
- foreach ( $this->get_bulk_actions() as $key => $bulk_action ) {
55
- $bulk_actions[] = array(
56
- 'key' => $key,
57
- 'label' => sprintf( $bulk_action['label'], $wp_post_types['job_listing']->labels->name ),
58
- );
59
- }
60
-
61
- if ( 'job_listing' === $post_type ) {
62
- ?>
63
- <script type="text/javascript">
64
- jQuery(document).ready(function() {
65
- var actions = <?php echo wp_json_encode( $bulk_actions ); ?>;
66
- actions.forEach(function(el){
67
- jQuery( '<option>').val( el.key ).text(el.label).appendTo("select[name='action']");
68
- jQuery( '<option>').val( el.key ).text(el.label).appendTo("select[name='action2']");
69
- });
70
- });
71
- </script>
72
- <?php
73
- }
74
- }
75
-
76
- /**
77
- * Performs bulk actions on Job Listing admin page.
78
- */
79
- public function do_bulk_actions_legacy() {
80
- $wp_list_table = _get_list_table( 'WP_Posts_List_Table' );
81
- $action = $wp_list_table->current_action();
82
- $actions_handled = $this->get_bulk_actions();
83
- if ( isset( $actions_handled[ $action ] ) && isset( $actions_handled[ $action ]['handler'] ) ) {
84
- check_admin_referer( 'bulk-posts' );
85
- $post_ids = array_map( 'absint', array_filter( (array) $_GET['post'] ) );
86
- if ( ! empty( $post_ids ) ) {
87
- $this->do_bulk_actions( admin_url( 'edit.php?post_type=job_listing' ), $action, $post_ids );
88
- }
89
- }
90
- }
91
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/admin/class-wp-job-manager-cpt.php CHANGED
@@ -1,455 +1,247 @@
1
  <?php
2
 
3
- if ( ! defined( 'ABSPATH' ) ) {
4
- exit; // Exit if accessed directly.
5
- }
6
 
7
  /**
8
- * Handles actions and filters specific to the custom post type for Job Listings.
9
- *
10
- * @package wp-job-manager
11
- * @since 1.0.0
12
  */
13
  class WP_Job_Manager_CPT {
14
 
15
  /**
16
- * The single instance of the class.
17
- *
18
- * @var self
19
- * @since 1.26.0
20
- */
21
- private static $_instance = null;
22
-
23
- /**
24
- * Allows for accessing single instance of class. Class should only be constructed once per call.
25
  *
26
- * @since 1.26.0
27
- * @static
28
- * @return self Main instance.
29
- */
30
- public static function instance() {
31
- if ( is_null( self::$_instance ) ) {
32
- self::$_instance = new self();
33
- }
34
- return self::$_instance;
35
- }
36
-
37
- /**
38
- * Constructor.
39
  */
40
  public function __construct() {
41
  add_filter( 'enter_title_here', array( $this, 'enter_title_here' ), 1, 2 );
42
  add_filter( 'manage_edit-job_listing_columns', array( $this, 'columns' ) );
43
- add_filter( 'list_table_primary_column', array( $this, 'primary_column' ), 10, 2 );
44
- add_filter( 'post_row_actions', array( $this, 'row_actions' ) );
45
  add_action( 'manage_job_listing_posts_custom_column', array( $this, 'custom_columns' ), 2 );
46
  add_filter( 'manage_edit-job_listing_sortable_columns', array( $this, 'sortable_columns' ) );
47
  add_filter( 'request', array( $this, 'sort_columns' ) );
48
  add_action( 'parse_query', array( $this, 'search_meta' ) );
49
- add_action( 'parse_query', array( $this, 'filter_meta' ) );
50
  add_filter( 'get_search_query', array( $this, 'search_meta_label' ) );
51
  add_filter( 'post_updated_messages', array( $this, 'post_updated_messages' ) );
52
- add_action( 'bulk_actions-edit-job_listing', array( $this, 'add_bulk_actions' ) );
53
- add_action( 'handle_bulk_actions-edit-job_listing', array( $this, 'do_bulk_actions' ), 10, 3 );
54
  add_action( 'admin_init', array( $this, 'approve_job' ) );
55
- add_action( 'admin_notices', array( $this, 'action_notices' ) );
56
- add_action( 'view_mode_post_types', array( $this, 'disable_view_mode' ) );
57
 
58
  if ( get_option( 'job_manager_enable_categories' ) ) {
59
- add_action( 'restrict_manage_posts', array( $this, 'jobs_by_category' ) );
60
  }
61
- add_action( 'restrict_manage_posts', array( $this, 'jobs_meta_filters' ) );
62
 
63
  foreach ( array( 'post', 'post-new' ) as $hook ) {
64
- add_action( "admin_footer-{$hook}.php", array( $this, 'extend_submitdiv_post_status' ) );
65
  }
66
  }
67
 
68
  /**
69
- * Returns the list of bulk actions that can be performed on job listings.
70
- *
71
- * @return array
72
- */
73
- public function get_bulk_actions() {
74
- $actions_handled = array();
75
- $actions_handled['approve_jobs'] = array(
76
- // translators: Placeholder (%s) is the plural name of the job listings post type.
77
- 'label' => __( 'Approve %s', 'wp-job-manager' ),
78
- // translators: Placeholder (%s) is the plural name of the job listings post type.
79
- 'notice' => __( '%s approved', 'wp-job-manager' ),
80
- 'handler' => array( $this, 'bulk_action_handle_approve_job' ),
81
- );
82
- $actions_handled['expire_jobs'] = array(
83
- // translators: Placeholder (%s) is the plural name of the job listings post type.
84
- 'label' => __( 'Expire %s', 'wp-job-manager' ),
85
- // translators: Placeholder (%s) is the plural name of the job listings post type.
86
- 'notice' => __( '%s expired', 'wp-job-manager' ),
87
- 'handler' => array( $this, 'bulk_action_handle_expire_job' ),
88
- );
89
- $actions_handled['mark_jobs_filled'] = array(
90
- // translators: Placeholder (%s) is the plural name of the job listings post type.
91
- 'label' => __( 'Mark %s Filled', 'wp-job-manager' ),
92
- // translators: Placeholder (%s) is the plural name of the job listings post type.
93
- 'notice' => __( '%s marked as filled', 'wp-job-manager' ),
94
- 'handler' => array( $this, 'bulk_action_handle_mark_job_filled' ),
95
- );
96
- $actions_handled['mark_jobs_not_filled'] = array(
97
- // translators: Placeholder (%s) is the plural name of the job listings post type.
98
- 'label' => __( 'Mark %s Not Filled', 'wp-job-manager' ),
99
- // translators: Placeholder (%s) is the plural name of the job listings post type.
100
- 'notice' => __( '%s marked as not filled', 'wp-job-manager' ),
101
- 'handler' => array( $this, 'bulk_action_handle_mark_job_not_filled' ),
102
- );
103
-
104
- /**
105
- * Filters the bulk actions that can be applied to job listings.
106
- *
107
- * @since 1.27.0
108
- *
109
- * @param array $actions_handled {
110
- * Bulk actions that can be handled, indexed by a unique key name (approve_jobs, expire_jobs, etc). Handlers
111
- * are responsible for checking abilities (`current_user_can( 'manage_job_listings', $post_id )`) before
112
- * performing action.
113
- *
114
- * @type string $label Label for the bulk actions dropdown. Passed through sprintf with label name of job listing post type.
115
- * @type string $notice Success notice shown after performing the action. Passed through sprintf with title(s) of affected job listings.
116
- * @type callback $handler Callable handler for performing action. Passed one argument (int $post_id) and should return true on success and false on failure.
117
- * }
118
- */
119
- return apply_filters( 'wpjm_job_listing_bulk_actions', $actions_handled );
120
- }
121
-
122
- /**
123
- * Adds bulk actions to drop downs on Job Listing admin page.
124
- *
125
- * @param array $bulk_actions
126
- * @return array
127
  */
128
- public function add_bulk_actions( $bulk_actions ) {
129
- global $wp_post_types;
130
-
131
- foreach ( $this->get_bulk_actions() as $key => $bulk_action ) {
132
- if ( isset( $bulk_action['label'] ) ) {
133
- $bulk_actions[ $key ] = sprintf( $bulk_action['label'], $wp_post_types['job_listing']->labels->name );
134
- }
 
 
 
 
 
 
 
 
135
  }
136
- return $bulk_actions;
137
  }
138
 
139
  /**
140
- * Performs bulk actions on Job Listing admin page.
141
- *
142
- * @since 1.27.0
143
- *
144
- * @param string $redirect_url The redirect URL.
145
- * @param string $action The action being taken.
146
- * @param array $post_ids The posts to take the action on.
147
  */
148
- public function do_bulk_actions( $redirect_url, $action, $post_ids ) {
149
- $actions_handled = $this->get_bulk_actions();
150
- if ( isset( $actions_handled[ $action ] ) && isset( $actions_handled[ $action ]['handler'] ) ) {
151
- $handled_jobs = array();
152
- if ( ! empty( $post_ids ) ) {
153
- foreach ( $post_ids as $post_id ) {
154
- if ( 'job_listing' === get_post_type( $post_id )
155
- && call_user_func( $actions_handled[ $action ]['handler'], $post_id ) ) {
156
- $handled_jobs[] = $post_id;
 
 
 
 
 
 
 
 
 
 
 
157
  }
158
- }
159
- wp_redirect( add_query_arg( 'handled_jobs', $handled_jobs, add_query_arg( 'action_performed', $action, $redirect_url ) ) );
160
- exit;
161
- }
162
- }
163
- }
164
-
165
- /**
166
- * Performs bulk action to approve a single job listing.
167
- *
168
- * @param int $post_id Post ID.
169
- *
170
- * @return bool
171
- */
172
- public function bulk_action_handle_approve_job( $post_id ) {
173
- $job_data = array(
174
- 'ID' => $post_id,
175
- 'post_status' => 'publish',
176
- );
177
- if ( in_array( get_post_status( $post_id ), array( 'pending', 'pending_payment' ), true )
178
- && current_user_can( 'publish_post', $post_id )
179
- && wp_update_post( $job_data )
180
- ) {
181
- return true;
182
- }
183
- return false;
184
- }
185
 
186
- /**
187
- * Performs bulk action to expire a single job listing.
188
- *
189
- * @param int $post_id Post ID.
190
- * @return bool
191
- */
192
- public function bulk_action_handle_expire_job( $post_id ) {
193
- $job_data = array(
194
- 'ID' => $post_id,
195
- 'post_status' => 'expired',
196
- );
197
- if ( current_user_can( 'manage_job_listings', $post_id )
198
- && wp_update_post( $job_data )
199
- ) {
200
- return true;
201
- }
202
- return false;
203
- }
204
 
205
- /**
206
- * Performs bulk action to mark a single job listing as filled.
207
- *
208
- * @param int $post_id Post ID.
209
- *
210
- * @return bool
211
- */
212
- public function bulk_action_handle_mark_job_filled( $post_id ) {
213
- if ( current_user_can( 'manage_job_listings', $post_id )
214
- && update_post_meta( $post_id, '_filled', 1 )
215
- ) {
216
- return true;
217
  }
218
- return false;
219
- }
220
 
221
- /**
222
- * Performs bulk action to mark a single job listing as not filled.
223
- *
224
- * @param int $post_id Post ID.
225
- * @return bool
226
- */
227
- public function bulk_action_handle_mark_job_not_filled( $post_id ) {
228
- if ( current_user_can( 'manage_job_listings', $post_id )
229
- && update_post_meta( $post_id, '_filled', 0 )
230
- ) {
231
- return true;
232
- }
233
- return false;
234
  }
235
 
236
  /**
237
- * Approves a single job.
238
  */
239
  public function approve_job() {
240
  if ( ! empty( $_GET['approve_job'] ) && wp_verify_nonce( $_REQUEST['_wpnonce'], 'approve_job' ) && current_user_can( 'publish_post', $_GET['approve_job'] ) ) {
241
- $post_id = absint( $_GET['approve_job'] );
242
  $job_data = array(
243
  'ID' => $post_id,
244
- 'post_status' => 'publish',
245
  );
246
  wp_update_post( $job_data );
247
- wp_redirect( remove_query_arg( 'approve_job', add_query_arg( 'handled_jobs', $post_id, add_query_arg( 'action_performed', 'approve_jobs', admin_url( 'edit.php?post_type=job_listing' ) ) ) ) );
248
  exit;
249
  }
250
  }
251
 
252
  /**
253
- * Shows a notice if we did a bulk action.
254
  */
255
- public function action_notices() {
256
- global $post_type, $pagenow;
257
-
258
- $handled_jobs = isset( $_REQUEST['handled_jobs'] ) ? $_REQUEST['handled_jobs'] : false;
259
- $action = isset( $_REQUEST['action_performed'] ) ? $_REQUEST['action_performed'] : false;
260
- $actions_handled = $this->get_bulk_actions();
261
-
262
- if ( 'edit.php' === $pagenow
263
- && 'job_listing' === $post_type
264
- && $action
265
- && ! empty( $handled_jobs )
266
- && isset( $actions_handled[ $action ] )
267
- && isset( $actions_handled[ $action ]['notice'] )
268
- ) {
269
- if ( is_array( $handled_jobs ) ) {
270
- $handled_jobs = array_map( 'absint', $handled_jobs );
271
- $titles = array();
272
- foreach ( $handled_jobs as $job_id ) {
273
- $titles[] = wpjm_get_the_job_title( $job_id );
274
- }
275
- echo '<div class="updated"><p>' . wp_kses_post( sprintf( $actions_handled[ $action ]['notice'], '&quot;' . implode( '&quot;, &quot;', $titles ) . '&quot;' ) ) . '</p></div>';
 
 
 
 
 
 
 
 
 
 
276
  } else {
277
- echo '<div class="updated"><p>' . wp_kses_post( sprintf( $actions_handled[ $action ]['notice'], '&quot;' . wpjm_get_the_job_title( absint( $handled_jobs ) ) . '&quot;' ) ) . '</p></div>';
278
  }
279
  }
280
  }
281
 
282
  /**
283
- * Shows category dropdown.
284
  */
285
  public function jobs_by_category() {
286
  global $typenow, $wp_query;
287
 
288
- if ( 'job_listing' !== $typenow || ! taxonomy_exists( 'job_listing_category' ) ) {
289
- return;
290
- }
291
 
292
- include_once JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-category-walker.php';
293
 
294
  $r = array();
295
- $r['taxonomy'] = 'job_listing_category';
296
  $r['pad_counts'] = 1;
297
  $r['hierarchical'] = 1;
298
  $r['hide_empty'] = 0;
299
  $r['show_count'] = 1;
300
  $r['selected'] = ( isset( $wp_query->query['job_listing_category'] ) ) ? $wp_query->query['job_listing_category'] : '';
301
  $r['menu_order'] = false;
302
- $terms = get_terms( $r );
303
- $walker = new WP_Job_Manager_Category_Walker();
304
 
305
  if ( ! $terms ) {
306
  return;
307
  }
308
 
309
- echo "<select name='job_listing_category' id='dropdown_job_listing_category'>";
310
- echo '<option value="" ' . selected( isset( $_GET['job_listing_category'] ) ? $_GET['job_listing_category'] : '', '', false ) . '>' . esc_html__( 'Select category', 'wp-job-manager' ) . '</option>';
311
- echo wp_kses_post( $walker->walk( $terms, 0, $r ) );
312
- echo '</select>';
313
 
 
314
  }
315
 
316
  /**
317
- * Output dropdowns for filters based on post meta.
318
- *
319
- * @since 1.31.0
320
- */
321
- public function jobs_meta_filters() {
322
- global $typenow;
323
-
324
- // Only add the filters for job_listings.
325
- if ( 'job_listing' !== $typenow ) {
326
- return;
327
- }
328
-
329
- // Filter by Filled.
330
- $this->jobs_filter_dropdown(
331
- 'job_listing_filled',
332
- array(
333
- array(
334
- 'value' => '',
335
- 'text' => __( 'Select Filled', 'wp-job-manager' ),
336
- ),
337
- array(
338
- 'value' => '1',
339
- 'text' => __( 'Filled', 'wp-job-manager' ),
340
- ),
341
- array(
342
- 'value' => '0',
343
- 'text' => __( 'Not Filled', 'wp-job-manager' ),
344
- ),
345
- )
346
- );
347
-
348
- // Filter by Featured.
349
- $this->jobs_filter_dropdown(
350
- 'job_listing_featured',
351
- array(
352
- array(
353
- 'value' => '',
354
- 'text' => __( 'Select Featured', 'wp-job-manager' ),
355
- ),
356
- array(
357
- 'value' => '1',
358
- 'text' => __( 'Featured', 'wp-job-manager' ),
359
- ),
360
- array(
361
- 'value' => '0',
362
- 'text' => __( 'Not Featured', 'wp-job-manager' ),
363
- ),
364
- )
365
- );
366
- }
367
-
368
- /**
369
- * Shows dropdown to filter by the given URL parameter. The dropdown will
370
- * have three options: "Select $name", "$name", and "Not $name".
371
- *
372
- * The $options element should be an array of arrays, each with the
373
- * attributes needed to create an <option> HTML element. The attributes are
374
- * as follows:
375
  *
376
- * $options[i]['value'] The value for the <option> HTML element.
377
- * $options[i]['text'] The text for the <option> HTML element.
378
- *
379
- * @since 1.31.0
380
- *
381
- * @param string $param The URL parameter.
382
- * @param array $options The options for the dropdown. See the description above.
383
- */
384
- private function jobs_filter_dropdown( $param, $options ) {
385
- $selected = isset( $_GET[ $param ] ) ? $_GET[ $param ] : '';
386
-
387
- echo '<select name="' . esc_attr( $param ) . '" id="dropdown_' . esc_attr( $param ) . '">';
388
-
389
- foreach ( $options as $option ) {
390
- echo '<option value="' . esc_attr( $option['value'] ) . '"'
391
- . ( $selected === $option['value'] ? ' selected' : '' )
392
- . '>' . esc_html( $option['text'] ) . '</option>';
393
- }
394
- echo '</select>';
395
-
396
- }
397
-
398
- /**
399
- * Filters page title placeholder text to show custom label.
400
- *
401
- * @param string $text
402
- * @param WP_Post|int $post
403
- * @return string
404
  */
405
  public function enter_title_here( $text, $post ) {
406
- if ( 'job_listing' === $post->post_type ) {
407
- return esc_html__( 'Position', 'wp-job-manager' );
408
- }
409
  return $text;
410
  }
411
 
412
  /**
413
- * Filters the post updated message array to add custom post type's messages.
414
  *
415
- * @param array $messages
416
- * @return array
 
417
  */
418
  public function post_updated_messages( $messages ) {
419
  global $post, $post_ID, $wp_post_types;
420
 
421
  $messages['job_listing'] = array(
422
- 0 => '',
423
- // translators: %1$s is the singular name of the job listing post type; %2$s is the URL to view the listing.
424
- 1 => sprintf( __( '%1$s updated. <a href="%2$s">View</a>', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name, esc_url( get_permalink( $post_ID ) ) ),
425
- 2 => __( 'Custom field updated.', 'wp-job-manager' ),
426
- 3 => __( 'Custom field deleted.', 'wp-job-manager' ),
427
- // translators: %s is the singular name of the job listing post type.
428
- 4 => sprintf( esc_html__( '%s updated.', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name ),
429
- // translators: %1$s is the singular name of the job listing post type; %2$s is the revision number.
430
- 5 => isset( $_GET['revision'] ) ? sprintf( __( '%1$s restored to revision from %2$s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name, wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
431
- // translators: %1$s is the singular name of the job listing post type; %2$s is the URL to view the listing.
432
- 6 => sprintf( __( '%1$s published. <a href="%2$s">View</a>', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name, esc_url( get_permalink( $post_ID ) ) ),
433
- // translators: %1$s is the singular name of the job listing post type; %2$s is the URL to view the listing.
434
- 7 => sprintf( esc_html__( '%s saved.', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name ),
435
- // translators: %1$s is the singular name of the job listing post type; %2$s is the URL to preview the listing.
436
- 8 => sprintf( __( '%1$s submitted. <a target="_blank" href="%2$s">Preview</a>', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name, esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),
437
- 9 => sprintf(
438
- // translators: %1$s is the singular name of the post type; %2$s is the date the post will be published; %3$s is the URL to preview the listing.
439
- __( '%1$s scheduled for: <strong>%2$s</strong>. <a target="_blank" href="%3$s">Preview</a>', 'wp-job-manager' ),
440
- $wp_post_types['job_listing']->labels->singular_name,
441
- date_i18n( get_option( 'date_format' ) . ' @ ' . get_option( 'time_format' ), strtotime( $post->post_date ) ),
442
- esc_url( get_permalink( $post_ID ) )
443
- ),
444
- // translators: %1$s is the singular name of the job listing post type; %2$s is the URL to view the listing.
445
- 10 => sprintf( __( '%1$s draft updated. <a target="_blank" href="%2$s">Preview</a>', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name, esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),
446
  );
447
 
448
  return $messages;
449
  }
450
 
451
  /**
452
- * Adds columns to admin listing of Job Listings.
453
  *
454
  * @param array $columns
455
  * @return array
@@ -461,81 +253,43 @@ class WP_Job_Manager_CPT {
461
 
462
  unset( $columns['title'], $columns['date'], $columns['author'] );
463
 
464
- $columns['job_position'] = __( 'Position', 'wp-job-manager' );
465
- $columns['job_listing_type'] = __( 'Type', 'wp-job-manager' );
466
- $columns['job_location'] = __( 'Location', 'wp-job-manager' );
467
- $columns['job_status'] = '<span class="tips" data-tip="' . __( 'Status', 'wp-job-manager' ) . '">' . __( 'Status', 'wp-job-manager' ) . '</span>';
468
- $columns['job_posted'] = __( 'Posted', 'wp-job-manager' );
469
- $columns['job_expires'] = __( 'Expires', 'wp-job-manager' );
470
- $columns['job_listing_category'] = __( 'Categories', 'wp-job-manager' );
471
- $columns['featured_job'] = '<span class="tips" data-tip="' . __( 'Featured?', 'wp-job-manager' ) . '">' . __( 'Featured?', 'wp-job-manager' ) . '</span>';
472
- $columns['filled'] = '<span class="tips" data-tip="' . __( 'Filled?', 'wp-job-manager' ) . '">' . __( 'Filled?', 'wp-job-manager' ) . '</span>';
473
- $columns['job_actions'] = __( 'Actions', 'wp-job-manager' );
474
 
475
  if ( ! get_option( 'job_manager_enable_categories' ) ) {
476
- unset( $columns['job_listing_category'] );
477
- }
478
-
479
- if ( ! get_option( 'job_manager_enable_types' ) ) {
480
- unset( $columns['job_listing_type'] );
481
  }
482
 
483
  return $columns;
484
  }
485
 
486
  /**
487
- * This is required to make column responsive since WP 4.3
488
- *
489
- * @access public
490
- * @param string $column
491
- * @param string $screen
492
- * @return string
493
- */
494
- public function primary_column( $column, $screen ) {
495
- if ( 'edit-job_listing' === $screen ) {
496
- $column = 'job_position';
497
- }
498
- return $column;
499
- }
500
-
501
- /**
502
- * Removes all action links because WordPress add it to primary column.
503
- * Note: Removing all actions also remove mobile "Show more details" toggle button.
504
- * So the button need to be added manually in custom_columns callback for primary column.
505
  *
506
  * @access public
507
- * @param array $actions
508
- * @return array
509
- */
510
- public function row_actions( $actions ) {
511
- if ( 'job_listing' === get_post_type() ) {
512
- return array();
513
- }
514
- return $actions;
515
- }
516
-
517
- /**
518
- * Displays the content for each custom column on the admin list for Job Listings.
519
- *
520
  * @param mixed $column
 
521
  */
522
  public function custom_columns( $column ) {
523
  global $post;
524
 
525
  switch ( $column ) {
526
- case 'job_listing_type':
527
- $types = wpjm_get_the_job_types( $post );
528
-
529
- if ( $types && ! empty( $types ) ) {
530
- foreach ( $types as $type ) {
531
- echo '<span class="job-type ' . esc_attr( $type->slug ) . '">' . esc_html( $type->name ) . '</span>';
532
- }
533
- }
534
- break;
535
- case 'job_position':
536
  echo '<div class="job_position">';
537
- // translators: %d is the post ID for the job listing.
538
- echo '<a href="' . esc_url( admin_url( 'post.php?post=' . $post->ID . '&action=edit' ) ) . '" class="tips job_title" data-tip="' . sprintf( esc_html__( 'ID: %d', 'wp-job-manager' ), intval( $post->ID ) ) . '">' . esc_html( wpjm_get_the_job_title() ) . '</a>';
539
 
540
  echo '<div class="company">';
541
 
@@ -549,79 +303,63 @@ class WP_Job_Manager_CPT {
549
 
550
  the_company_logo();
551
  echo '</div>';
552
- echo '<button type="button" class="toggle-row"><span class="screen-reader-text">Show more details</span></button>';
553
- break;
554
- case 'job_location':
555
- the_job_location( true, $post );
556
- break;
557
- case 'job_listing_category':
558
- $terms = get_the_term_list( $post->ID, $column, '', ', ', '' );
559
- if ( ! $terms ) {
560
- echo '<span class="na">&ndash;</span>';
561
- } else {
562
- echo wp_kses_post( $terms );
563
- }
564
- break;
565
- case 'filled':
566
- if ( is_position_filled( $post ) ) {
567
- echo '&#10004;';
568
- } else {
 
 
 
 
569
  echo '&ndash;';
570
- }
571
- break;
572
- case 'featured_job':
573
- if ( is_position_featured( $post ) ) {
574
- echo '&#10004;';
575
- } else {
576
- echo '&ndash;';
577
- }
578
- break;
579
- case 'job_posted':
580
- echo '<strong>' . esc_html( date_i18n( get_option( 'date_format' ), strtotime( $post->post_date ) ) ) . '</strong><span>';
581
- // translators: %s placeholder is the username of the user.
582
- echo ( empty( $post->post_author ) ? esc_html__( 'by a guest', 'wp-job-manager' ) : sprintf( esc_html__( 'by %s', 'wp-job-manager' ), '<a href="' . esc_url( add_query_arg( 'author', $post->post_author ) ) . '">' . esc_html( get_the_author() ) . '</a>' ) ) . '</span>';
583
- break;
584
- case 'job_expires':
585
- if ( $post->_job_expires ) {
586
- echo '<strong>' . esc_html( date_i18n( get_option( 'date_format' ), strtotime( $post->_job_expires ) ) ) . '</strong>';
587
- } else {
588
- echo '&ndash;';
589
- }
590
- break;
591
- case 'job_status':
592
- echo '<span data-tip="' . esc_attr( get_the_job_status( $post ) ) . '" class="tips status-' . esc_attr( $post->post_status ) . '">' . esc_html( get_the_job_status( $post ) ) . '</span>';
593
- break;
594
- case 'job_actions':
595
  echo '<div class="actions">';
596
  $admin_actions = apply_filters( 'post_row_actions', array(), $post );
597
 
598
- if ( in_array( $post->post_status, array( 'pending', 'pending_payment' ), true ) && current_user_can( 'publish_post', $post->ID ) ) {
599
- $admin_actions['approve'] = array(
600
- 'action' => 'approve',
601
- 'name' => __( 'Approve', 'wp-job-manager' ),
602
- 'url' => wp_nonce_url( add_query_arg( 'approve_job', $post->ID ), 'approve_job' ),
603
  );
604
  }
605
- if ( 'trash' !== $post->post_status ) {
606
  if ( current_user_can( 'read_post', $post->ID ) ) {
607
- $admin_actions['view'] = array(
608
- 'action' => 'view',
609
- 'name' => __( 'View', 'wp-job-manager' ),
610
- 'url' => get_permalink( $post->ID ),
611
  );
612
  }
613
  if ( current_user_can( 'edit_post', $post->ID ) ) {
614
- $admin_actions['edit'] = array(
615
- 'action' => 'edit',
616
- 'name' => __( 'Edit', 'wp-job-manager' ),
617
- 'url' => get_edit_post_link( $post->ID ),
618
  );
619
  }
620
  if ( current_user_can( 'delete_post', $post->ID ) ) {
621
  $admin_actions['delete'] = array(
622
- 'action' => 'delete',
623
- 'name' => __( 'Delete', 'wp-job-manager' ),
624
- 'url' => get_delete_post_link( $post->ID ),
625
  );
626
  }
627
  }
@@ -630,66 +368,61 @@ class WP_Job_Manager_CPT {
630
 
631
  foreach ( $admin_actions as $action ) {
632
  if ( is_array( $action ) ) {
633
- printf( '<a class="button button-icon tips icon-%1$s" href="%2$s" data-tip="%3$s">%4$s</a>', esc_attr( $action['action'] ), esc_url( $action['url'] ), esc_attr( $action['name'] ), esc_html( $action['name'] ) );
634
  } else {
635
- echo wp_kses_post( str_replace( 'class="', 'class="button ', $action ) );
636
  }
637
  }
638
 
639
  echo '</div>';
640
 
641
- break;
642
  }
643
  }
644
 
645
  /**
646
- * Filters the list table sortable columns for the admin list of Job Listings.
647
  *
 
648
  * @param mixed $columns
649
- * @return array
650
  */
651
  public function sortable_columns( $columns ) {
652
  $custom = array(
653
  'job_posted' => 'date',
654
  'job_position' => 'title',
655
  'job_location' => 'job_location',
656
- 'job_expires' => 'job_expires',
657
  );
658
  return wp_parse_args( $custom, $columns );
659
  }
660
 
661
  /**
662
- * Sorts the admin listing of Job Listings by updating the main query in the request.
663
  *
664
- * @param mixed $vars Variables with sort arguments.
665
- * @return array
 
666
  */
667
  public function sort_columns( $vars ) {
668
  if ( isset( $vars['orderby'] ) ) {
669
  if ( 'job_expires' === $vars['orderby'] ) {
670
- $vars = array_merge(
671
- $vars,
672
- array(
673
- 'meta_key' => '_job_expires',
674
- 'orderby' => 'meta_value',
675
- )
676
- );
677
  } elseif ( 'job_location' === $vars['orderby'] ) {
678
- $vars = array_merge(
679
- $vars,
680
- array(
681
- 'meta_key' => '_job_location',
682
- 'orderby' => 'meta_value',
683
- )
684
- );
685
  }
686
  }
687
  return $vars;
688
  }
689
 
690
  /**
691
- * Searches custom fields as well as content.
692
- *
693
  * @param WP_Query $wp
694
  */
695
  public function search_meta( $wp ) {
@@ -699,136 +432,83 @@ class WP_Job_Manager_CPT {
699
  return;
700
  }
701
 
702
- $post_ids = array_unique(
703
- array_merge(
704
- $wpdb->get_col(
705
- $wpdb->prepare(
706
- "
707
  SELECT posts.ID
708
  FROM {$wpdb->posts} posts
709
  INNER JOIN {$wpdb->postmeta} p1 ON posts.ID = p1.post_id
710
- WHERE p1.meta_value LIKE %s
711
- OR posts.post_title LIKE %s
712
- OR posts.post_content LIKE %s
713
  AND posts.post_type = 'job_listing'
714
  ",
715
- '%' . $wpdb->esc_like( $wp->query_vars['s'] ) . '%',
716
- '%' . $wpdb->esc_like( $wp->query_vars['s'] ) . '%',
717
- '%' . $wpdb->esc_like( $wp->query_vars['s'] ) . '%'
718
- )
719
- ),
720
- array( 0 )
721
- )
722
- );
723
 
724
- // Adjust the query vars.
725
  unset( $wp->query_vars['s'] );
726
  $wp->query_vars['job_listing_search'] = true;
727
- $wp->query_vars['post__in'] = $post_ids;
728
  }
729
 
730
  /**
731
- * Filters by meta fields.
732
- *
733
- * @param WP_Query $wp
734
- */
735
- public function filter_meta( $wp ) {
736
- global $pagenow;
737
-
738
- if ( 'edit.php' !== $pagenow || empty( $wp->query_vars['post_type'] ) || 'job_listing' !== $wp->query_vars['post_type'] ) {
739
- return;
740
- }
741
-
742
- $meta_query = $wp->get( 'meta_query' );
743
- if ( ! is_array( $meta_query ) ) {
744
- $meta_query = array();
745
- }
746
-
747
- // Filter on _filled meta.
748
- if ( isset( $_GET['job_listing_filled'] ) && '' !== $_GET['job_listing_filled'] ) {
749
- $meta_query[] = array(
750
- 'key' => '_filled',
751
- 'value' => $_GET['job_listing_filled'],
752
- );
753
- }
754
-
755
- // Filter on _featured meta.
756
- if ( isset( $_GET['job_listing_featured'] ) && '' !== $_GET['job_listing_featured'] ) {
757
- $meta_query[] = array(
758
- 'key' => '_featured',
759
- 'value' => $_GET['job_listing_featured'],
760
- );
761
- }
762
-
763
- // Set new meta query.
764
- if ( ! empty( $meta_query ) ) {
765
- $wp->set( 'meta_query', $meta_query );
766
- }
767
- }
768
-
769
- /**
770
- * Changes the label when searching meta.
771
- *
772
  * @param string $query
773
  * @return string
774
  */
775
  public function search_meta_label( $query ) {
776
  global $pagenow, $typenow;
777
 
778
- if ( 'edit.php' !== $pagenow || 'job_listing' !== $typenow || ! get_query_var( 'job_listing_search' ) ) {
779
  return $query;
780
  }
781
 
782
  return wp_unslash( sanitize_text_field( $_GET['s'] ) );
783
  }
784
 
785
- /**
786
  * Adds post status to the "submitdiv" Meta Box and post type WP List Table screens. Based on https://gist.github.com/franz-josef-kaiser/2930190
 
 
787
  */
788
  public function extend_submitdiv_post_status() {
789
  global $post, $post_type;
790
 
791
- // Abort if we're on the wrong post type, but only if we got a restriction.
792
  if ( 'job_listing' !== $post_type ) {
793
  return;
794
  }
795
 
796
- // Get all non-builtin post status and add them as <option>.
797
- $options = '';
798
- $display = '';
799
  foreach ( get_job_listing_post_statuses() as $status => $name ) {
800
  $selected = selected( $post->post_status, $status, false );
801
 
802
- // If we one of our custom post status is selected, remember it.
803
- if ( $selected ) {
804
- $display = $name;
805
- }
806
 
807
- // Build the options.
808
- $options .= "<option{$selected} value='{$status}'>" . esc_html( $name ) . '</option>';
809
  }
810
  ?>
811
  <script type="text/javascript">
812
  jQuery( document ).ready( function($) {
813
  <?php if ( ! empty( $display ) ) : ?>
814
- jQuery( '#post-status-display' ).html( <?php echo wp_json_encode( $display ); ?> );
815
  <?php endif; ?>
816
 
817
  var select = jQuery( '#post-status-select' ).find( 'select' );
818
- jQuery( select ).html( <?php echo wp_json_encode( $options ); ?> );
819
  } );
820
  </script>
821
  <?php
822
  }
823
-
824
- /**
825
- * Removes job_listing from the list of post types that support "View Mode" option
826
- *
827
- * @param array $post_types Array of post types that support view mode.
828
- * @return array Array of post types that support view mode, without job_listing post type.
829
- */
830
- public function disable_view_mode( $post_types ) {
831
- unset( $post_types['job_listing'] );
832
- return $post_types;
833
- }
834
  }
 
 
1
  <?php
2
 
3
+ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
4
 
5
  /**
6
+ * WP_Job_Manager_CPT class.
 
 
 
7
  */
8
  class WP_Job_Manager_CPT {
9
 
10
  /**
11
+ * __construct function.
 
 
 
 
 
 
 
 
12
  *
13
+ * @access public
14
+ * @return void
 
 
 
 
 
 
 
 
 
 
 
15
  */
16
  public function __construct() {
17
  add_filter( 'enter_title_here', array( $this, 'enter_title_here' ), 1, 2 );
18
  add_filter( 'manage_edit-job_listing_columns', array( $this, 'columns' ) );
 
 
19
  add_action( 'manage_job_listing_posts_custom_column', array( $this, 'custom_columns' ), 2 );
20
  add_filter( 'manage_edit-job_listing_sortable_columns', array( $this, 'sortable_columns' ) );
21
  add_filter( 'request', array( $this, 'sort_columns' ) );
22
  add_action( 'parse_query', array( $this, 'search_meta' ) );
 
23
  add_filter( 'get_search_query', array( $this, 'search_meta_label' ) );
24
  add_filter( 'post_updated_messages', array( $this, 'post_updated_messages' ) );
25
+ add_action( 'admin_footer-edit.php', array( $this, 'add_bulk_actions' ) );
26
+ add_action( 'load-edit.php', array( $this, 'do_bulk_actions' ) );
27
  add_action( 'admin_init', array( $this, 'approve_job' ) );
28
+ add_action( 'admin_notices', array( $this, 'approved_notice' ) );
29
+ add_action( 'admin_notices', array( $this, 'expired_notice' ) );
30
 
31
  if ( get_option( 'job_manager_enable_categories' ) ) {
32
+ add_action( "restrict_manage_posts", array( $this, "jobs_by_category" ) );
33
  }
 
34
 
35
  foreach ( array( 'post', 'post-new' ) as $hook ) {
36
+ add_action( "admin_footer-{$hook}.php", array( $this,'extend_submitdiv_post_status' ) );
37
  }
38
  }
39
 
40
  /**
41
+ * Edit bulk actions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  */
43
+ public function add_bulk_actions() {
44
+ global $post_type, $wp_post_types;;
45
+
46
+ if ( $post_type == 'job_listing' ) {
47
+ ?>
48
+ <script type="text/javascript">
49
+ jQuery(document).ready(function() {
50
+ jQuery('<option>').val('approve_jobs').text('<?php printf( __( 'Approve %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name ); ?>').appendTo("select[name='action']");
51
+ jQuery('<option>').val('approve_jobs').text('<?php printf( __( 'Approve %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name ); ?>').appendTo("select[name='action2']");
52
+
53
+ jQuery('<option>').val('expire_jobs').text('<?php printf( __( 'Expire %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name ); ?>').appendTo("select[name='action']");
54
+ jQuery('<option>').val('expire_jobs').text('<?php printf( __( 'Expire %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name ); ?>').appendTo("select[name='action2']");
55
+ });
56
+ </script>
57
+ <?php
58
  }
 
59
  }
60
 
61
  /**
62
+ * Do custom bulk actions
 
 
 
 
 
 
63
  */
64
+ public function do_bulk_actions() {
65
+ $wp_list_table = _get_list_table( 'WP_Posts_List_Table' );
66
+ $action = $wp_list_table->current_action();
67
+
68
+ switch( $action ) {
69
+ case 'approve_jobs' :
70
+ check_admin_referer( 'bulk-posts' );
71
+
72
+ $post_ids = array_map( 'absint', array_filter( (array) $_GET['post'] ) );
73
+ $approved_jobs = array();
74
+
75
+ if ( ! empty( $post_ids ) )
76
+ foreach( $post_ids as $post_id ) {
77
+ $job_data = array(
78
+ 'ID' => $post_id,
79
+ 'post_status' => 'publish'
80
+ );
81
+ if ( in_array( get_post_status( $post_id ), array( 'pending', 'pending_payment' ) ) && current_user_can( 'publish_post', $post_id ) && wp_update_post( $job_data ) ) {
82
+ $approved_jobs[] = $post_id;
83
+ }
84
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
+ wp_redirect( add_query_arg( 'approved_jobs', $approved_jobs, remove_query_arg( array( 'approved_jobs', 'expired_jobs' ), admin_url( 'edit.php?post_type=job_listing' ) ) ) );
87
+ exit;
88
+ break;
89
+ case 'expire_jobs' :
90
+ check_admin_referer( 'bulk-posts' );
91
+
92
+ $post_ids = array_map( 'absint', array_filter( (array) $_GET['post'] ) );
93
+ $expired_jobs = array();
94
+
95
+ if ( ! empty( $post_ids ) )
96
+ foreach( $post_ids as $post_id ) {
97
+ $job_data = array(
98
+ 'ID' => $post_id,
99
+ 'post_status' => 'expired'
100
+ );
101
+ if ( current_user_can( 'manage_job_listings' ) && wp_update_post( $job_data ) )
102
+ $expired_jobs[] = $post_id;
103
+ }
104
 
105
+ wp_redirect( add_query_arg( 'expired_jobs', $expired_jobs, remove_query_arg( array( 'approved_jobs', 'expired_jobs' ), admin_url( 'edit.php?post_type=job_listing' ) ) ) );
106
+ exit;
107
+ break;
 
 
 
 
 
 
 
 
 
108
  }
 
 
109
 
110
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
111
  }
112
 
113
  /**
114
+ * Approve a single job
115
  */
116
  public function approve_job() {
117
  if ( ! empty( $_GET['approve_job'] ) && wp_verify_nonce( $_REQUEST['_wpnonce'], 'approve_job' ) && current_user_can( 'publish_post', $_GET['approve_job'] ) ) {
118
+ $post_id = absint( $_GET['approve_job'] );
119
  $job_data = array(
120
  'ID' => $post_id,
121
+ 'post_status' => 'publish'
122
  );
123
  wp_update_post( $job_data );
124
+ wp_redirect( remove_query_arg( 'approve_job', add_query_arg( 'approved_jobs', $post_id, admin_url( 'edit.php?post_type=job_listing' ) ) ) );
125
  exit;
126
  }
127
  }
128
 
129
  /**
130
+ * Show a notice if we did a bulk action or approval
131
  */
132
+ public function approved_notice() {
133
+ global $post_type, $pagenow;
134
+
135
+ if ( $pagenow == 'edit.php' && $post_type == 'job_listing' && ! empty( $_REQUEST['approved_jobs'] ) ) {
136
+ $approved_jobs = $_REQUEST['approved_jobs'];
137
+ if ( is_array( $approved_jobs ) ) {
138
+ $approved_jobs = array_map( 'absint', $approved_jobs );
139
+ $titles = array();
140
+ foreach ( $approved_jobs as $job_id )
141
+ $titles[] = get_the_title( $job_id );
142
+ echo '<div class="updated"><p>' . sprintf( __( '%s approved', 'wp-job-manager' ), '&quot;' . implode( '&quot;, &quot;', $titles ) . '&quot;' ) . '</p></div>';
143
+ } else {
144
+ echo '<div class="updated"><p>' . sprintf( __( '%s approved', 'wp-job-manager' ), '&quot;' . get_the_title( $approved_jobs ) . '&quot;' ) . '</p></div>';
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Show a notice if we did a bulk action or approval
151
+ */
152
+ public function expired_notice() {
153
+ global $post_type, $pagenow;
154
+
155
+ if ( $pagenow == 'edit.php' && $post_type == 'job_listing' && ! empty( $_REQUEST['expired_jobs'] ) ) {
156
+ $expired_jobs = $_REQUEST['expired_jobs'];
157
+ if ( is_array( $expired_jobs ) ) {
158
+ $expired_jobs = array_map( 'absint', $expired_jobs );
159
+ $titles = array();
160
+ foreach ( $expired_jobs as $job_id )
161
+ $titles[] = get_the_title( $job_id );
162
+ echo '<div class="updated"><p>' . sprintf( __( '%s expired', 'wp-job-manager' ), '&quot;' . implode( '&quot;, &quot;', $titles ) . '&quot;' ) . '</p></div>';
163
  } else {
164
+ echo '<div class="updated"><p>' . sprintf( __( '%s expired', 'wp-job-manager' ), '&quot;' . get_the_title( $expired_jobs ) . '&quot;' ) . '</p></div>';
165
  }
166
  }
167
  }
168
 
169
  /**
170
+ * Show category dropdown
171
  */
172
  public function jobs_by_category() {
173
  global $typenow, $wp_query;
174
 
175
+ if ( $typenow != 'job_listing' || ! taxonomy_exists( 'job_listing_category' ) ) {
176
+ return;
177
+ }
178
 
179
+ include_once( JOB_MANAGER_PLUGIN_DIR . '/includes/class-wp-job-manager-category-walker.php' );
180
 
181
  $r = array();
 
182
  $r['pad_counts'] = 1;
183
  $r['hierarchical'] = 1;
184
  $r['hide_empty'] = 0;
185
  $r['show_count'] = 1;
186
  $r['selected'] = ( isset( $wp_query->query['job_listing_category'] ) ) ? $wp_query->query['job_listing_category'] : '';
187
  $r['menu_order'] = false;
188
+ $terms = get_terms( 'job_listing_category', $r );
189
+ $walker = new WP_Job_Manager_Category_Walker;
190
 
191
  if ( ! $terms ) {
192
  return;
193
  }
194
 
195
+ $output = "<select name='job_listing_category' id='dropdown_job_listing_category'>";
196
+ $output .= '<option value="" ' . selected( isset( $_GET['job_listing_category'] ) ? $_GET['job_listing_category'] : '', '', false ) . '>' . __( 'Select category', 'wp-job-manager' ) . '</option>';
197
+ $output .= $walker->walk( $terms, 0, $r );
198
+ $output .= "</select>";
199
 
200
+ echo $output;
201
  }
202
 
203
  /**
204
+ * enter_title_here function.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  *
206
+ * @access public
207
+ * @return void
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  */
209
  public function enter_title_here( $text, $post ) {
210
+ if ( $post->post_type == 'job_listing' )
211
+ return __( 'Position', 'wp-job-manager' );
 
212
  return $text;
213
  }
214
 
215
  /**
216
+ * post_updated_messages function.
217
  *
218
+ * @access public
219
+ * @param mixed $messages
220
+ * @return void
221
  */
222
  public function post_updated_messages( $messages ) {
223
  global $post, $post_ID, $wp_post_types;
224
 
225
  $messages['job_listing'] = array(
226
+ 0 => '',
227
+ 1 => sprintf( __( '%s updated. <a href="%s">View</a>', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name, esc_url( get_permalink( $post_ID ) ) ),
228
+ 2 => __( 'Custom field updated.', 'wp-job-manager' ),
229
+ 3 => __( 'Custom field deleted.', 'wp-job-manager' ),
230
+ 4 => sprintf( __( '%s updated.', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name ),
231
+ 5 => isset( $_GET['revision'] ) ? sprintf( __( '%s restored to revision from %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name, wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
232
+ 6 => sprintf( __( '%s published. <a href="%s">View</a>', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name, esc_url( get_permalink( $post_ID ) ) ),
233
+ 7 => sprintf( __( '%s saved.', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name ),
234
+ 8 => sprintf( __( '%s submitted. <a target="_blank" href="%s">Preview</a>', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name, esc_url( add_query_arg( 'preview', 'true', get_permalink($post_ID) ) ) ),
235
+ 9 => sprintf( __( '%s scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview</a>', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name,
236
+ date_i18n( __( 'M j, Y @ G:i', 'wp-job-manager' ), strtotime( $post->post_date ) ), esc_url( get_permalink( $post_ID ) ) ),
237
+ 10 => sprintf( __( '%s draft updated. <a target="_blank" href="%s">Preview</a>', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name, esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),
 
 
 
 
 
 
 
 
 
 
 
 
238
  );
239
 
240
  return $messages;
241
  }
242
 
243
  /**
244
+ * columns function.
245
  *
246
  * @param array $columns
247
  * @return array
253
 
254
  unset( $columns['title'], $columns['date'], $columns['author'] );
255
 
256
+ $columns["job_listing_type"] = __( "Type", 'wp-job-manager' );
257
+ $columns["job_position"] = __( "Position", 'wp-job-manager' );
258
+ $columns["job_location"] = __( "Location", 'wp-job-manager' );
259
+ $columns['job_status'] = '<span class="tips" data-tip="' . __( "Status", 'wp-job-manager' ) . '">' . __( "Status", 'wp-job-manager' ) . '</span>';
260
+ $columns["job_posted"] = __( "Posted", 'wp-job-manager' );
261
+ $columns["job_expires"] = __( "Expires", 'wp-job-manager' );
262
+ $columns["job_listing_category"] = __( "Categories", 'wp-job-manager' );
263
+ $columns['featured_job'] = '<span class="tips" data-tip="' . __( "Featured?", 'wp-job-manager' ) . '">' . __( "Featured?", 'wp-job-manager' ) . '</span>';
264
+ $columns['filled'] = '<span class="tips" data-tip="' . __( "Filled?", 'wp-job-manager' ) . '">' . __( "Filled?", 'wp-job-manager' ) . '</span>';
265
+ $columns['job_actions'] = __( "Actions", 'wp-job-manager' );
266
 
267
  if ( ! get_option( 'job_manager_enable_categories' ) ) {
268
+ unset( $columns["job_listing_category"] );
 
 
 
 
269
  }
270
 
271
  return $columns;
272
  }
273
 
274
  /**
275
+ * custom_columns function.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
  *
277
  * @access public
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  * @param mixed $column
279
+ * @return void
280
  */
281
  public function custom_columns( $column ) {
282
  global $post;
283
 
284
  switch ( $column ) {
285
+ case "job_listing_type" :
286
+ $type = get_the_job_type( $post );
287
+ if ( $type )
288
+ echo '<span class="job-type ' . $type->slug . '">' . $type->name . '</span>';
289
+ break;
290
+ case "job_position" :
 
 
 
 
291
  echo '<div class="job_position">';
292
+ echo '<a href="' . admin_url('post.php?post=' . $post->ID . '&action=edit') . '" class="tips job_title" data-tip="' . sprintf( __( 'ID: %d', 'wp-job-manager' ), $post->ID ) . '">' . $post->post_title . '</a>';
 
293
 
294
  echo '<div class="company">';
295
 
303
 
304
  the_company_logo();
305
  echo '</div>';
306
+ break;
307
+ case "job_location" :
308
+ the_job_location( $post );
309
+ break;
310
+ case "job_listing_category" :
311
+ if ( ! $terms = get_the_term_list( $post->ID, $column, '', ', ', '' ) ) echo '<span class="na">&ndash;</span>'; else echo $terms;
312
+ break;
313
+ case "filled" :
314
+ if ( is_position_filled( $post ) ) echo '&#10004;'; else echo '&ndash;';
315
+ break;
316
+ case "featured_job" :
317
+ if ( is_position_featured( $post ) ) echo '&#10004;'; else echo '&ndash;';
318
+ break;
319
+ case "job_posted" :
320
+ echo '<strong>' . date_i18n( __( 'M j, Y', 'wp-job-manager' ), strtotime( $post->post_date ) ) . '</strong><span>';
321
+ echo ( empty( $post->post_author ) ? __( 'by a guest', 'wp-job-manager' ) : sprintf( __( 'by %s', 'wp-job-manager' ), '<a href="' . esc_url( add_query_arg( 'author', $post->post_author ) ) . '">' . get_the_author() . '</a>' ) ) . '</span>';
322
+ break;
323
+ case "job_expires" :
324
+ if ( $post->_job_expires )
325
+ echo '<strong>' . date_i18n( __( 'M j, Y', 'wp-job-manager' ), strtotime( $post->_job_expires ) ) . '</strong>';
326
+ else
327
  echo '&ndash;';
328
+ break;
329
+ case "job_status" :
330
+ echo '<span data-tip="' . esc_attr( get_the_job_status( $post ) ) . '" class="tips status-' . esc_attr( $post->post_status ) . '">' . get_the_job_status( $post ) . '</span>';
331
+ break;
332
+ case "job_actions" :
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  echo '<div class="actions">';
334
  $admin_actions = apply_filters( 'post_row_actions', array(), $post );
335
 
336
+ if ( in_array( $post->post_status, array( 'pending', 'pending_payment' ) ) && current_user_can ( 'publish_post', $post->ID ) ) {
337
+ $admin_actions['approve'] = array(
338
+ 'action' => 'approve',
339
+ 'name' => __( 'Approve', 'wp-job-manager' ),
340
+ 'url' => wp_nonce_url( add_query_arg( 'approve_job', $post->ID ), 'approve_job' )
341
  );
342
  }
343
+ if ( $post->post_status !== 'trash' ) {
344
  if ( current_user_can( 'read_post', $post->ID ) ) {
345
+ $admin_actions['view'] = array(
346
+ 'action' => 'view',
347
+ 'name' => __( 'View', 'wp-job-manager' ),
348
+ 'url' => get_permalink( $post->ID )
349
  );
350
  }
351
  if ( current_user_can( 'edit_post', $post->ID ) ) {
352
+ $admin_actions['edit'] = array(
353
+ 'action' => 'edit',
354
+ 'name' => __( 'Edit', 'wp-job-manager' ),
355
+ 'url' => get_edit_post_link( $post->ID )
356
  );
357
  }
358
  if ( current_user_can( 'delete_post', $post->ID ) ) {
359
  $admin_actions['delete'] = array(
360
+ 'action' => 'delete',
361
+ 'name' => __( 'Delete', 'wp-job-manager' ),
362
+ 'url' => get_delete_post_link( $post->ID )
363
  );
364
  }
365
  }
368
 
369
  foreach ( $admin_actions as $action ) {
370
  if ( is_array( $action ) ) {
371
+ printf( '<a class="button button-icon tips icon-%1$s" href="%2$s" data-tip="%3$s">%4$s</a>', $action['action'], esc_url( $action['url'] ), esc_attr( $action['name'] ), esc_html( $action['name'] ) );
372
  } else {
373
+ echo str_replace( 'class="', 'class="button ', $action );
374
  }
375
  }
376
 
377
  echo '</div>';
378
 
379
+ break;
380
  }
381
  }
382
 
383
  /**
384
+ * sortable_columns function.
385
  *
386
+ * @access public
387
  * @param mixed $columns
388
+ * @return void
389
  */
390
  public function sortable_columns( $columns ) {
391
  $custom = array(
392
  'job_posted' => 'date',
393
  'job_position' => 'title',
394
  'job_location' => 'job_location',
395
+ 'job_expires' => 'job_expires'
396
  );
397
  return wp_parse_args( $custom, $columns );
398
  }
399
 
400
  /**
401
+ * sort_columns function.
402
  *
403
+ * @access public
404
+ * @param mixed $vars
405
+ * @return void
406
  */
407
  public function sort_columns( $vars ) {
408
  if ( isset( $vars['orderby'] ) ) {
409
  if ( 'job_expires' === $vars['orderby'] ) {
410
+ $vars = array_merge( $vars, array(
411
+ 'meta_key' => '_job_expires',
412
+ 'orderby' => 'meta_value'
413
+ ) );
 
 
 
414
  } elseif ( 'job_location' === $vars['orderby'] ) {
415
+ $vars = array_merge( $vars, array(
416
+ 'meta_key' => '_job_location',
417
+ 'orderby' => 'meta_value'
418
+ ) );
 
 
 
419
  }
420
  }
421
  return $vars;
422
  }
423
 
424
  /**
425
+ * Search custom fields as well as content.
 
426
  * @param WP_Query $wp
427
  */
428
  public function search_meta( $wp ) {
432
  return;
433
  }
434
 
435
+ $post_ids = array_unique( array_merge(
436
+ $wpdb->get_col(
437
+ $wpdb->prepare( "
 
 
438
  SELECT posts.ID
439
  FROM {$wpdb->posts} posts
440
  INNER JOIN {$wpdb->postmeta} p1 ON posts.ID = p1.post_id
441
+ WHERE p1.meta_value LIKE '%%%s%%'
442
+ OR posts.post_title LIKE '%%%s%%'
443
+ OR posts.post_content LIKE '%%%s%%'
444
  AND posts.post_type = 'job_listing'
445
  ",
446
+ esc_sql( $wp->query_vars['s'] ),
447
+ esc_sql( $wp->query_vars['s'] ),
448
+ esc_sql( $wp->query_vars['s'] )
449
+ )
450
+ ),
451
+ array( 0 )
452
+ ) );
 
453
 
454
+ // Adjust the query vars
455
  unset( $wp->query_vars['s'] );
456
  $wp->query_vars['job_listing_search'] = true;
457
+ $wp->query_vars['post__in'] = $post_ids;
458
  }
459
 
460
  /**
461
+ * Change the label when searching meta.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  * @param string $query
463
  * @return string
464
  */
465
  public function search_meta_label( $query ) {
466
  global $pagenow, $typenow;
467
 
468
+ if ( 'edit.php' !== $pagenow || $typenow !== 'job_listing' || ! get_query_var( 'job_listing_search' ) ) {
469
  return $query;
470
  }
471
 
472
  return wp_unslash( sanitize_text_field( $_GET['s'] ) );
473
  }
474
 
475
+ /**
476
  * Adds post status to the "submitdiv" Meta Box and post type WP List Table screens. Based on https://gist.github.com/franz-josef-kaiser/2930190
477
+ *
478
+ * @return void
479
  */
480
  public function extend_submitdiv_post_status() {
481
  global $post, $post_type;
482
 
483
+ // Abort if we're on the wrong post type, but only if we got a restriction
484
  if ( 'job_listing' !== $post_type ) {
485
  return;
486
  }
487
 
488
+ // Get all non-builtin post status and add them as <option>
489
+ $options = $display = '';
 
490
  foreach ( get_job_listing_post_statuses() as $status => $name ) {
491
  $selected = selected( $post->post_status, $status, false );
492
 
493
+ // If we one of our custom post status is selected, remember it
494
+ $selected AND $display = $name;
 
 
495
 
496
+ // Build the options
497
+ $options .= "<option{$selected} value='{$status}'>{$name}</option>";
498
  }
499
  ?>
500
  <script type="text/javascript">
501
  jQuery( document ).ready( function($) {
502
  <?php if ( ! empty( $display ) ) : ?>
503
+ jQuery( '#post-status-display' ).html( '<?php echo $display; ?>' );
504
  <?php endif; ?>
505
 
506
  var select = jQuery( '#post-status-select' ).find( 'select' );
507
+ jQuery( select ).html( "<?php echo $options; ?>" );
508
  } );
509
  </script>
510
  <?php
511
  }
 
 
 
 
 
 
 
 
 
 
 
512
  }
513
+
514
+ new WP_Job_Manager_CPT();
includes/admin/class-wp-job-manager-permalink-settings.php DELETED
@@ -1,135 +0,0 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) {
4
- exit; // Exit if accessed directly.
5
- }
6
-
7
- /**
8
- * Handles front admin page for WP Job Manager.
9
- *
10
- * @package wp-job-manager
11
- * @see https://github.com/woocommerce/woocommerce/blob/3.0.8/includes/admin/class-wc-admin-permalink-settings.php Based on WooCommerce's implementation.
12
- * @since 1.27.0
13
- */
14
- class WP_Job_Manager_Permalink_Settings {
15
- /**
16
- * The single instance of the class.
17
- *
18
- * @var self
19
- * @since 1.27.0
20
- */
21
- private static $_instance = null;
22
-
23
- /**
24
- * Permalink settings.
25
- *
26
- * @var array
27
- * @since 1.27.0
28
- */
29
- private $permalinks = array();
30
-
31
- /**
32
- * Allows for accessing single instance of class. Class should only be constructed once per call.
33
- *
34
- * @since 1.27.0
35
- * @static
36
- * @return self Main instance.
37
- */
38
- public static function instance() {
39
- if ( is_null( self::$_instance ) ) {
40
- self::$_instance = new self();
41
- }
42
- return self::$_instance;
43
- }
44
-
45
- /**
46
- * Constructor.
47
- */
48
- public function __construct() {
49
- $this->setup_fields();
50
- $this->settings_save();
51
- $this->permalinks = WP_Job_Manager_Post_Types::get_permalink_structure();
52
- }
53
-
54
- /**
55
- * Add setting fields related to permalinks.
56
- */
57
- public function setup_fields() {
58
- add_settings_field(
59
- 'wpjm_job_base_slug',
60
- __( 'Job base', 'wp-job-manager' ),
61
- array( $this, 'job_base_slug_input' ),
62
- 'permalink',
63
- 'optional'
64
- );
65
- add_settings_field(
66
- 'wpjm_job_category_slug',
67
- __( 'Job category base', 'wp-job-manager' ),
68
- array( $this, 'job_category_slug_input' ),
69
- 'permalink',
70
- 'optional'
71
- );
72
- add_settings_field(
73
- 'wpjm_job_type_slug',
74
- __( 'Job type base', 'wp-job-manager' ),
75
- array( $this, 'job_type_slug_input' ),
76
- 'permalink',
77
- 'optional'
78
- );
79
- }
80
-
81
- /**
82
- * Show a slug input box for job post type slug.
83
- */
84
- public function job_base_slug_input() {
85
- ?>
86
- <input name="wpjm_job_base_slug" type="text" class="regular-text code" value="<?php echo esc_attr( $this->permalinks['job_base'] ); ?>" placeholder="<?php echo esc_attr_x( 'job', 'Job permalink - resave permalinks after changing this', 'wp-job-manager' ); ?>" />
87
- <?php
88
- }
89
-
90
- /**
91
- * Show a slug input box for job category slug.
92
- */
93
- public function job_category_slug_input() {
94
- ?>
95
- <input name="wpjm_job_category_slug" type="text" class="regular-text code" value="<?php echo esc_attr( $this->permalinks['category_base'] ); ?>" placeholder="<?php echo esc_attr_x( 'job-category', 'Job category slug - resave permalinks after changing this', 'wp-job-manager' ); ?>" />
96
- <?php
97
- }
98
-
99
- /**
100
- * Show a slug input box for job type slug.
101
- */
102
- public function job_type_slug_input() {
103
- ?>
104
- <input name="wpjm_job_type_slug" type="text" class="regular-text code" value="<?php echo esc_attr( $this->permalinks['type_base'] ); ?>" placeholder="<?php echo esc_attr_x( 'job-type', 'Job type slug - resave permalinks after changing this', 'wp-job-manager' ); ?>" />
105
- <?php
106
- }
107
-
108
- /**
109
- * Save the settings.
110
- */
111
- public function settings_save() {
112
- if ( ! is_admin() ) {
113
- return;
114
- }
115
-
116
- if ( isset( $_POST['permalink_structure'] ) ) {
117
- if ( function_exists( 'switch_to_locale' ) ) {
118
- switch_to_locale( get_locale() );
119
- }
120
-
121
- $permalinks = (array) get_option( 'wpjm_permalinks', array() );
122
- $permalinks['job_base'] = sanitize_title_with_dashes( $_POST['wpjm_job_base_slug'] );
123
- $permalinks['category_base'] = sanitize_title_with_dashes( $_POST['wpjm_job_category_slug'] );
124
- $permalinks['type_base'] = sanitize_title_with_dashes( $_POST['wpjm_job_type_slug'] );
125
-
126
- update_option( 'wpjm_permalinks', $permalinks );
127
-
128
- if ( function_exists( 'restore_current_locale' ) ) {
129
- restore_current_locale();
130
- }
131
- }
132
- }
133
- }
134
-
135
- WP_Job_Manager_Permalink_Settings::instance();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/admin/class-wp-job-manager-settings.php CHANGED
@@ -1,47 +1,17 @@
1
  <?php
2
- if ( ! defined( 'ABSPATH' ) ) {
3
- exit; // Exit if accessed directly.
4
- }
5
 
6
  /**
7
- * Handles the management of plugin settings.
8
- *
9
- * @package wp-job-manager
10
- * @since 1.0.0
11
  */
12
  class WP_Job_Manager_Settings {
13
 
14
  /**
15
- * The single instance of the class.
16
- *
17
- * @var self
18
- * @since 1.26.0
19
- */
20
- private static $_instance = null;
21
-
22
- /**
23
- * Our Settings.
24
  *
25
- * @var array Settings.
26
- */
27
- protected $settings = array();
28
-
29
- /**
30
- * Allows for accessing single instance of class. Class should only be constructed once per call.
31
- *
32
- * @since 1.26.0
33
- * @static
34
- * @return self Main instance.
35
- */
36
- public static function instance() {
37
- if ( is_null( self::$_instance ) ) {
38
- self::$_instance = new self();
39
- }
40
- return self::$_instance;
41
- }
42
-
43
- /**
44
- * Constructor.
45
  */
46
  public function __construct() {
47
  $this->settings_group = 'job_manager';
@@ -49,71 +19,26 @@ class WP_Job_Manager_Settings {
49
  }
50
 
51
  /**
52
- * Get Job Manager Settings
53
- *
54
- * @return array
55
- */
56
- public function get_settings() {
57
- if ( 0 === count( $this->settings ) ) {
58
- $this->init_settings();
59
- }
60
- return $this->settings;
61
- }
62
-
63
- /**
64
- * Initializes the configuration for the plugin's setting fields.
65
  *
66
  * @access protected
 
67
  */
68
  protected function init_settings() {
69
- // Prepare roles option.
70
  $roles = get_editable_roles();
71
  $account_roles = array();
72
 
73
  foreach ( $roles as $key => $role ) {
74
- if ( 'administrator' === $key ) {
75
  continue;
76
  }
77
  $account_roles[ $key ] = $role['name'];
78
  }
79
 
80
- $this->settings = apply_filters(
81
- 'job_manager_settings',
82
  array(
83
- 'general' => array(
84
- __( 'General', 'wp-job-manager' ),
85
- array(
86
- array(
87
- 'name' => 'job_manager_date_format',
88
- 'std' => 'relative',
89
- 'label' => __( 'Date Format', 'wp-job-manager' ),
90
- 'desc' => __( 'Choose how you want the published date for jobs to be displayed on the front-end.', 'wp-job-manager' ),
91
- 'type' => 'radio',
92
- 'options' => array(
93
- 'relative' => __( 'Relative to the current date (e.g., 1 day, 1 week, 1 month ago)', 'wp-job-manager' ),
94
- 'default' => __( 'Default date format as defined in Settings', 'wp-job-manager' ),
95
- ),
96
- ),
97
- array(
98
- 'name' => 'job_manager_google_maps_api_key',
99
- 'std' => '',
100
- 'label' => __( 'Google Maps API Key', 'wp-job-manager' ),
101
- // translators: Placeholder %s is URL to set up a Google Maps API key.
102
- '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' ),
103
- 'attributes' => array(),
104
- ),
105
- array(
106
- 'name' => 'job_manager_delete_data_on_uninstall',
107
- 'std' => '0',
108
- 'label' => __( 'Delete Data On Uninstall', 'wp-job-manager' ),
109
- 'cb_label' => __( 'Delete WP Job Manager data when the plugin is deleted. Once removed, this data cannot be restored.', 'wp-job-manager' ),
110
- 'desc' => '',
111
- 'type' => 'checkbox',
112
- 'attributes' => array(),
113
- ),
114
- ),
115
- ),
116
- 'job_listings' => array(
117
  __( 'Job Listings', 'wp-job-manager' ),
118
  array(
119
  array(
@@ -121,82 +46,55 @@ class WP_Job_Manager_Settings {
121
  'std' => '10',
122
  'placeholder' => '',
123
  'label' => __( 'Listings Per Page', 'wp-job-manager' ),
124
- 'desc' => __( 'Number of job listings to display per page.', 'wp-job-manager' ),
125
- 'attributes' => array(),
126
  ),
127
  array(
128
  'name' => 'job_manager_hide_filled_positions',
129
  'std' => '0',
130
  'label' => __( 'Filled Positions', 'wp-job-manager' ),
131
  'cb_label' => __( 'Hide filled positions', 'wp-job-manager' ),
132
- 'desc' => __( 'Filled positions will not display in your archives.', 'wp-job-manager' ),
133
- 'type' => 'checkbox',
134
- 'attributes' => array(),
135
- ),
136
- array(
137
- 'name' => 'job_manager_hide_expired',
138
- 'std' => get_option( 'job_manager_hide_expired_content' ) ? '1' : '0', // back compat.
139
- 'label' => __( 'Hide Expired Listings', 'wp-job-manager' ),
140
- 'cb_label' => __( 'Hide expired listings in job archives/search', 'wp-job-manager' ),
141
- 'desc' => __( 'Expired job listings will not be searchable.', 'wp-job-manager' ),
142
  'type' => 'checkbox',
143
- 'attributes' => array(),
144
  ),
145
  array(
146
  'name' => 'job_manager_hide_expired_content',
147
  'std' => '1',
148
- 'label' => __( 'Hide Expired Listings Content', 'wp-job-manager' ),
149
- 'cb_label' => __( 'Hide content in expired single job listings', 'wp-job-manager' ),
150
- 'desc' => __( 'Your site will display the titles of expired listings, but not the content of the listings. Otherwise, expired listings display their full content minus the application area.', 'wp-job-manager' ),
151
  'type' => 'checkbox',
152
- 'attributes' => array(),
153
  ),
154
  array(
155
  'name' => 'job_manager_enable_categories',
156
  'std' => '0',
157
  'label' => __( 'Categories', 'wp-job-manager' ),
158
- 'cb_label' => __( 'Enable listing categories', 'wp-job-manager' ),
159
- 'desc' => __( 'This lets users select from a list of categories when submitting a job. Note: an admin has to create categories before site users can select them.', 'wp-job-manager' ),
160
  'type' => 'checkbox',
161
- 'attributes' => array(),
162
  ),
163
  array(
164
  'name' => 'job_manager_enable_default_category_multiselect',
165
  'std' => '0',
166
  'label' => __( 'Multi-select Categories', 'wp-job-manager' ),
167
- 'cb_label' => __( 'Default to category multiselect', 'wp-job-manager' ),
168
- 'desc' => __( 'The category selection box will default to allowing multiple selections on the [jobs] shortcode. Without this, users will only be able to select a single category when submitting jobs.', 'wp-job-manager' ),
169
  'type' => 'checkbox',
170
- 'attributes' => array(),
171
  ),
172
  array(
173
- 'name' => 'job_manager_category_filter_type',
174
- 'std' => 'any',
175
- 'label' => __( 'Category Filter Type', 'wp-job-manager' ),
176
- 'desc' => __( 'Determines the logic used to display jobs when selecting multiple categories.', 'wp-job-manager' ),
177
- 'type' => 'radio',
178
  'options' => array(
179
- 'any' => __( 'Jobs will be shown if within ANY selected category', 'wp-job-manager' ),
180
  'all' => __( 'Jobs will be shown if within ALL selected categories', 'wp-job-manager' ),
181
- ),
182
- ),
183
- array(
184
- 'name' => 'job_manager_enable_types',
185
- 'std' => '1',
186
- 'label' => __( 'Types', 'wp-job-manager' ),
187
- 'cb_label' => __( 'Enable listing types', 'wp-job-manager' ),
188
- 'desc' => __( 'This lets users select from a list of types when submitting a job. Note: an admin has to create types before site users can select them.', 'wp-job-manager' ),
189
- 'type' => 'checkbox',
190
- 'attributes' => array(),
191
- ),
192
- array(
193
- 'name' => 'job_manager_multi_job_type',
194
- 'std' => '0',
195
- 'label' => __( 'Multi-select Listing Types', 'wp-job-manager' ),
196
- 'cb_label' => __( 'Allow multiple types for listings', 'wp-job-manager' ),
197
- 'desc' => __( 'This allows users to select more than one type when submitting a job. The metabox on the post editor and the selection box on the front-end job submission form will both reflect this.', 'wp-job-manager' ),
198
- 'type' => 'checkbox',
199
- 'attributes' => array(),
200
  ),
201
  ),
202
  ),
@@ -207,660 +105,283 @@ class WP_Job_Manager_Settings {
207
  'name' => 'job_manager_user_requires_account',
208
  'std' => '1',
209
  'label' => __( 'Account Required', 'wp-job-manager' ),
210
- 'cb_label' => __( 'Require an account to submit listings', 'wp-job-manager' ),
211
- 'desc' => __( 'Limits job listing submissions to registered, logged-in users.', 'wp-job-manager' ),
212
  'type' => 'checkbox',
213
- 'attributes' => array(),
214
  ),
215
  array(
216
  'name' => 'job_manager_enable_registration',
217
  'std' => '1',
218
  'label' => __( 'Account Creation', 'wp-job-manager' ),
219
- 'cb_label' => __( 'Enable account creation during submission', 'wp-job-manager' ),
220
- 'desc' => __( 'Includes account creation on the listing submission form, to allow non-registered users to create an account and submit a job listing simultaneously.', 'wp-job-manager' ),
221
  'type' => 'checkbox',
222
- 'attributes' => array(),
223
  ),
224
  array(
225
  'name' => 'job_manager_generate_username_from_email',
226
  'std' => '1',
227
  'label' => __( 'Account Username', 'wp-job-manager' ),
228
- 'cb_label' => __( 'Generate usernames from email addresses', 'wp-job-manager' ),
229
- 'desc' => __( 'Automatically generates usernames for new accounts from the registrant\'s email address. If this is not enabled, a "username" field will display instead.', 'wp-job-manager' ),
230
  'type' => 'checkbox',
231
- 'attributes' => array(),
232
  ),
233
  array(
234
- 'name' => 'job_manager_use_standard_password_setup_email',
235
- 'std' => '1',
236
- 'label' => __( 'Account Password', 'wp-job-manager' ),
237
- 'cb_label' => __( 'Email new users a link to set a password', 'wp-job-manager' ),
238
- 'desc' => __( 'Sends an email to the user with their username and a link to set their password. If this is not enabled, a "password" field will display instead, and their email address won\'t be verified.', 'wp-job-manager' ),
239
- 'type' => 'checkbox',
240
- 'attributes' => array(),
241
- ),
242
- array(
243
- 'name' => 'job_manager_registration_role',
244
- 'std' => 'employer',
245
- 'label' => __( 'Account Role', 'wp-job-manager' ),
246
- 'desc' => __( 'Any new accounts created during submission will have this role. If you haven\'t enabled account creation during submission in the options above, your own method of assigning roles will apply.', 'wp-job-manager' ),
247
- 'type' => 'select',
248
- 'options' => $account_roles,
249
  ),
250
  array(
251
  'name' => 'job_manager_submission_requires_approval',
252
  'std' => '1',
253
  'label' => __( 'Moderate New Listings', 'wp-job-manager' ),
254
- 'cb_label' => __( 'Require admin approval of all new listing submissions', 'wp-job-manager' ),
255
- 'desc' => __( 'Sets all new submissions to "pending." They will not appear on your site until an admin approves them.', 'wp-job-manager' ),
256
  'type' => 'checkbox',
257
- 'attributes' => array(),
258
  ),
259
  array(
260
  'name' => 'job_manager_user_can_edit_pending_submissions',
261
  'std' => '0',
262
  'label' => __( 'Allow Pending Edits', 'wp-job-manager' ),
263
- 'cb_label' => __( 'Allow editing of pending listings', 'wp-job-manager' ),
264
- 'desc' => __( 'Users can continue to edit pending listings until they are approved by an admin.', 'wp-job-manager' ),
265
  'type' => 'checkbox',
266
- 'attributes' => array(),
267
- ),
268
- array(
269
- 'name' => 'job_manager_user_edit_published_submissions',
270
- 'std' => 'yes',
271
- 'label' => __( 'Allow Published Edits', 'wp-job-manager' ),
272
- 'cb_label' => __( 'Allow editing of published listings', 'wp-job-manager' ),
273
- 'desc' => __( 'Choose whether published job listings can be edited and if edits require admin approval. When moderation is required, the original job listings will be unpublished while edits await admin approval.', 'wp-job-manager' ),
274
- 'type' => 'radio',
275
- 'options' => array(
276
- 'no' => __( 'Users cannot edit', 'wp-job-manager' ),
277
- 'yes' => __( 'Users can edit without admin approval', 'wp-job-manager' ),
278
- 'yes_moderated' => __( 'Users can edit, but edits require admin approval', 'wp-job-manager' ),
279
- ),
280
- 'attributes' => array(),
281
  ),
282
  array(
283
  'name' => 'job_manager_submission_duration',
284
  'std' => '30',
285
  'label' => __( 'Listing Duration', 'wp-job-manager' ),
286
- 'desc' => __( 'Listings will display for the set number of days, then expire. Leave this field blank if you don\'t want listings to have an expiration date.', 'wp-job-manager' ),
287
- 'attributes' => array(),
288
  ),
289
  array(
290
- 'name' => 'job_manager_allowed_application_method',
291
- 'std' => '',
292
- 'label' => __( 'Application Method', 'wp-job-manager' ),
293
- 'desc' => __( 'Choose the application method job listers will need to provide. Specify URL or email address only, or allow listers to choose which they prefer.', 'wp-job-manager' ),
294
- 'type' => 'radio',
295
- 'options' => array(
296
  '' => __( 'Email address or website URL', 'wp-job-manager' ),
297
  'email' => __( 'Email addresses only', 'wp-job-manager' ),
298
  'url' => __( 'Website URLs only', 'wp-job-manager' ),
299
- ),
300
- ),
301
- ),
302
  ),
303
- 'recaptcha' => array(
304
- __( 'reCAPTCHA', 'wp-job-manager' ),
305
- array(
306
- array(
307
- 'name' => 'job_manager_recaptcha_label',
308
- 'std' => __( 'Are you human?', 'wp-job-manager' ),
309
- 'placeholder' => '',
310
- 'label' => __( 'Field Label', 'wp-job-manager' ),
311
- 'desc' => __( 'The label used for the reCAPTCHA field on forms.', 'wp-job-manager' ),
312
- 'attributes' => array(),
313
- ),
314
- array(
315
- 'name' => 'job_manager_recaptcha_site_key',
316
- 'std' => '',
317
- 'placeholder' => '',
318
- 'label' => __( 'Site Key', 'wp-job-manager' ),
319
- // translators: Placeholder %s is URL to set up Google reCAPTCHA API key.
320
- 'desc' => sprintf( __( 'You can retrieve your site key from <a href="%s">Google\'s reCAPTCHA admin dashboard</a>.', 'wp-job-manager' ), 'https://www.google.com/recaptcha/admin#list' ),
321
- 'attributes' => array(),
322
- ),
323
- array(
324
- 'name' => 'job_manager_recaptcha_secret_key',
325
- 'std' => '',
326
- 'placeholder' => '',
327
- 'label' => __( 'Secret Key', 'wp-job-manager' ),
328
- // translators: Placeholder %s is URL to set up Google reCAPTCHA API key.
329
- 'desc' => sprintf( __( 'You can retrieve your secret key from <a href="%s">Google\'s reCAPTCHA admin dashboard</a>.', 'wp-job-manager' ), 'https://www.google.com/recaptcha/admin#list' ),
330
- 'attributes' => array(),
331
- ),
332
- array(
333
- 'name' => 'job_manager_enable_recaptcha_job_submission',
334
- 'std' => '0',
335
- 'label' => __( 'Job Submission Form', 'wp-job-manager' ),
336
- 'cb_label' => __( 'Display a reCAPTCHA field on job submission form.', 'wp-job-manager' ),
337
- 'desc' => sprintf( __( 'This will help prevent bots from submitting job listings. You must have entered a valid site key and secret key above.', 'wp-job-manager' ), 'https://www.google.com/recaptcha/admin#list' ),
338
- 'type' => 'checkbox',
339
- 'attributes' => array(),
340
- ),
341
- ),
342
- ),
343
- 'job_pages' => array(
344
  __( 'Pages', 'wp-job-manager' ),
345
  array(
346
  array(
347
- 'name' => 'job_manager_submit_job_form_page_id',
348
- 'std' => '',
349
- 'label' => __( 'Submit Job Form Page', 'wp-job-manager' ),
350
- 'desc' => __( 'Select the page where you\'ve used the [submit_job_form] shortcode. This lets the plugin know the location of the form.', 'wp-job-manager' ),
351
- 'type' => 'page',
352
  ),
353
  array(
354
- 'name' => 'job_manager_job_dashboard_page_id',
355
- 'std' => '',
356
- 'label' => __( 'Job Dashboard Page', 'wp-job-manager' ),
357
- 'desc' => __( 'Select the page where you\'ve used the [job_dashboard] shortcode. This lets the plugin know the location of the dashboard.', 'wp-job-manager' ),
358
- 'type' => 'page',
359
  ),
360
  array(
361
- 'name' => 'job_manager_jobs_page_id',
362
- 'std' => '',
363
- 'label' => __( 'Job Listings Page', 'wp-job-manager' ),
364
- 'desc' => __( 'Select the page where you\'ve used the [jobs] shortcode. This lets the plugin know the location of the job listings page.', 'wp-job-manager' ),
365
- 'type' => 'page',
366
  ),
367
- ),
368
- ),
369
  )
370
  );
371
  }
372
 
373
  /**
374
- * Registers the plugin's settings with WordPress's Settings API.
 
 
 
375
  */
376
  public function register_settings() {
377
  $this->init_settings();
378
 
379
  foreach ( $this->settings as $section ) {
380
  foreach ( $section[1] as $option ) {
381
- if ( isset( $option['std'] ) ) {
382
  add_option( $option['name'], $option['std'] );
383
- }
384
  register_setting( $this->settings_group, $option['name'] );
385
  }
386
  }
387
  }
388
 
389
  /**
390
- * Shows the plugin's settings page.
 
 
 
391
  */
392
  public function output() {
393
  $this->init_settings();
394
  ?>
395
  <div class="wrap job-manager-settings-wrap">
396
- <form class="job-manager-options" method="post" action="options.php">
397
 
398
  <?php settings_fields( $this->settings_group ); ?>
399
 
400
- <h2 class="nav-tab-wrapper">
401
- <?php
402
- foreach ( $this->settings as $key => $section ) {
403
- echo '<a href="#settings-' . esc_attr( sanitize_title( $key ) ) . '" class="nav-tab">' . esc_html( $section[0] ) . '</a>';
404
- }
405
- ?>
406
- </h2>
407
 
408
  <?php
409
- if ( ! empty( $_GET['settings-updated'] ) ) {
410
- flush_rewrite_rules();
411
- echo '<div class="updated fade job-manager-updated"><p>' . esc_html__( 'Settings successfully saved', 'wp-job-manager' ) . '</p></div>';
412
- }
413
-
414
- foreach ( $this->settings as $key => $section ) {
415
- $section_args = isset( $section[2] ) ? (array) $section[2] : array();
416
- echo '<div id="settings-' . esc_attr( sanitize_title( $key ) ) . '" class="settings_panel">';
417
- if ( ! empty( $section_args['before'] ) ) {
418
- echo '<p class="before-settings">' . wp_kses_post( $section_args['before'] ) . '</p>';
419
  }
420
- echo '<table class="form-table settings parent-settings">';
421
 
422
- foreach ( $section[1] as $option ) {
423
- $value = get_option( $option['name'] );
424
- $this->output_field( $option, $value );
425
- }
426
 
427
- echo '</table>';
428
- if ( ! empty( $section_args['after'] ) ) {
429
- echo '<p class="after-settings">' . wp_kses_post( $section_args['after'] ) . '</p>';
430
- }
431
- echo '</div>';
432
 
433
- }
434
- ?>
435
- <p class="submit">
436
- <input type="submit" class="button-primary" value="<?php esc_attr_e( 'Save Changes', 'wp-job-manager' ); ?>" />
437
- </p>
438
- </form>
439
- </div>
440
- <script type="text/javascript">
441
- jQuery('.nav-tab-wrapper a').click(function() {
442
- if ( '#' !== jQuery(this).attr( 'href' ).substr( 0, 1 ) ) {
443
- return false;
444
- }
445
- jQuery('.settings_panel').hide();
446
- jQuery('.nav-tab-active').removeClass('nav-tab-active');
447
- jQuery( jQuery(this).attr('href') ).show();
448
- jQuery(this).addClass('nav-tab-active');
449
- window.location.hash = jQuery(this).attr('href');
450
- jQuery( 'form.job-manager-options' ).attr( 'action', 'options.php' + jQuery(this).attr( 'href' ) );
451
- window.scrollTo( 0, 0 );
452
- return false;
453
- });
454
- var goto_hash = window.location.hash;
455
- if ( '#' === goto_hash.substr( 0, 1 ) ) {
456
- jQuery( 'form.job-manager-options' ).attr( 'action', 'options.php' + jQuery(this).attr( 'href' ) );
457
- }
458
- if ( goto_hash ) {
459
- var the_tab = jQuery( 'a[href="' + goto_hash + '"]' );
460
- if ( the_tab.length > 0 ) {
461
- the_tab.click();
462
- } else {
463
- jQuery( '.nav-tab-wrapper a:first' ).click();
464
- }
465
- } else {
466
- jQuery( '.nav-tab-wrapper a:first' ).click();
467
- }
468
- var $use_standard_password_setup_email = jQuery('#setting-job_manager_use_standard_password_setup_email');
469
- var $generate_username_from_email = jQuery('#setting-job_manager_generate_username_from_email');
470
- var $job_manager_registration_role = jQuery('#setting-job_manager_registration_role');
471
 
472
- jQuery('#setting-job_manager_enable_registration').change(function(){
473
- if ( jQuery( this ).is(':checked') ) {
474
- $job_manager_registration_role.closest('tr').show();
475
- $use_standard_password_setup_email.closest('tr').show();
476
- $generate_username_from_email.closest('tr').show();
477
- } else {
478
- $job_manager_registration_role.closest('tr').hide();
479
- $use_standard_password_setup_email.closest('tr').hide();
480
- $generate_username_from_email.closest('tr').hide();
481
- }
482
- }).change();
483
 
484
- // If generate username is enabled on page load, assume use_standard_password_setup_email has been cleared.
485
- // Default is true, so let's sneakily set it to that before it gets cleared and disabled.
486
- if ( $generate_username_from_email.is(':checked') ) {
487
- $use_standard_password_setup_email.prop('checked', true);
488
- }
489
 
490
- $generate_username_from_email.change(function() {
491
- if ( jQuery( this ).is(':checked') ) {
492
- $use_standard_password_setup_email.data('original-state', $use_standard_password_setup_email.is(':checked')).prop('checked', true).prop('disabled', true);
493
- } else {
494
- $use_standard_password_setup_email.prop('disabled', false);
495
- if ( undefined !== $use_standard_password_setup_email.data('original-state') ) {
496
- $use_standard_password_setup_email.prop('checked', $use_standard_password_setup_email.data('original-state'));
497
- }
498
- }
499
- }).change();
500
 
501
- jQuery( '.sub-settings-expander' ).on( 'change', function() {
502
- var $expandable = jQuery(this).parent().siblings( '.sub-settings-expandable' );
503
- var checked = jQuery(this).is( ':checked' );
504
- if ( checked ) {
505
- $expandable.addClass( 'expanded' );
506
- } else {
507
- $expandable.removeClass( 'expanded' );
508
- }
509
- } ).trigger( 'change' );
510
- </script>
511
- <?php
512
- }
513
 
514
- /**
515
- * Checkbox input field.
516
- *
517
- * @param array $option
518
- * @param array $attributes
519
- * @param mixed $value
520
- * @param string $ignored_placeholder
521
- */
522
- protected function input_checkbox( $option, $attributes, $value, $ignored_placeholder ) {
523
- ?>
524
- <label>
525
- <input type="hidden" name="<?php echo esc_attr( $option['name'] ); ?>" value="0" />
526
- <input
527
- id="setting-<?php echo esc_attr( $option['name'] ); ?>"
528
- name="<?php echo esc_attr( $option['name'] ); ?>"
529
- type="checkbox"
530
- value="1"
531
- <?php
532
- echo implode( ' ', $attributes ) . ' '; // WPCS: XSS ok.
533
- checked( '1', $value );
534
- ?>
535
- /> <?php echo wp_kses_post( $option['cb_label'] ); ?></label>
536
- <?php
537
- if ( ! empty( $option['desc'] ) ) {
538
- echo ' <p class="description">' . wp_kses_post( $option['desc'] ) . '</p>';
539
- }
540
- }
541
 
542
- /**
543
- * Text area input field.
544
- *
545
- * @param array $option
546
- * @param array $attributes
547
- * @param mixed $value
548
- * @param string $placeholder
549
- */
550
- protected function input_textarea( $option, $attributes, $value, $placeholder ) {
551
- ?>
552
- <textarea
553
- id="setting-<?php echo esc_attr( $option['name'] ); ?>"
554
- class="large-text"
555
- cols="50"
556
- rows="3"
557
- name="<?php echo esc_attr( $option['name'] ); ?>"
558
- <?php
559
- echo implode( ' ', $attributes ) . ' '; // WPCS: XSS ok.
560
- echo $placeholder; // WPCS: XSS ok.
561
- ?>
562
- >
563
- <?php echo esc_textarea( $value ); ?>
564
- </textarea>
565
- <?php
566
 
567
- if ( ! empty( $option['desc'] ) ) {
568
- echo ' <p class="description">' . wp_kses_post( $option['desc'] ) . '</p>';
569
- }
570
- }
571
 
572
- /**
573
- * Select input field.
574
- *
575
- * @param array $option
576
- * @param array $attributes
577
- * @param mixed $value
578
- * @param string $ignored_placeholder
579
- */
580
- protected function input_select( $option, $attributes, $value, $ignored_placeholder ) {
581
- ?>
582
- <select
583
- id="setting-<?php echo esc_attr( $option['name'] ); ?>"
584
- class="regular-text"
585
- name="<?php echo esc_attr( $option['name'] ); ?>"
586
- <?php
587
- echo implode( ' ', $attributes ); // WPCS: XSS ok.
588
- ?>
589
- >
590
- <?php
591
- foreach ( $option['options'] as $key => $name ) {
592
- echo '<option value="' . esc_attr( $key ) . '" ' . selected( $value, $key, false ) . '>' . esc_html( $name ) . '</option>';
593
- }
594
- ?>
595
- </select>
596
- <?php
597
 
598
- if ( ! empty( $option['desc'] ) ) {
599
- echo ' <p class="description">' . wp_kses_post( $option['desc'] ) . '</p>';
600
- }
601
- }
602
 
603
- /**
604
- * Radio input field.
605
- *
606
- * @param array $option
607
- * @param array $ignored_attributes
608
- * @param mixed $value
609
- * @param string $ignored_placeholder
610
- */
611
- protected function input_radio( $option, $ignored_attributes, $value, $ignored_placeholder ) {
612
- ?>
613
- <fieldset>
614
- <legend class="screen-reader-text">
615
- <span><?php echo esc_html( $option['label'] ); ?></span>
616
- </legend>
617
- <?php
618
- if ( ! empty( $option['desc'] ) ) {
619
- echo ' <p class="description">' . wp_kses_post( $option['desc'] ) . '</p>';
620
- }
621
 
622
- foreach ( $option['options'] as $key => $name ) {
623
- echo '<label><input name="' . esc_attr( $option['name'] ) . '" type="radio" value="' . esc_attr( $key ) . '" ' . checked( $value, $key, false ) . ' />' . esc_html( $name ) . '</label><br>';
624
- }
625
- ?>
626
- </fieldset>
627
- <?php
628
- }
629
 
630
- /**
631
- * Page input field.
632
- *
633
- * @param array $option
634
- * @param array $ignored_attributes
635
- * @param mixed $value
636
- * @param string $ignored_placeholder
637
- */
638
- protected function input_page( $option, $ignored_attributes, $value, $ignored_placeholder ) {
639
- $args = array(
640
- 'name' => $option['name'],
641
- 'id' => $option['name'],
642
- 'sort_column' => 'menu_order',
643
- 'sort_order' => 'ASC',
644
- 'show_option_none' => __( '--no page--', 'wp-job-manager' ),
645
- 'echo' => false,
646
- 'selected' => absint( $value ),
647
- );
648
 
649
- echo str_replace( ' id=', " data-placeholder='" . esc_attr__( 'Select a page&hellip;', 'wp-job-manager' ) . "' id=", wp_dropdown_pages( $args ) ); // WPCS: XSS ok.
 
 
 
650
 
651
- if ( ! empty( $option['desc'] ) ) {
652
- echo ' <p class="description">' . wp_kses_post( $option['desc'] ) . '</p>';
653
- }
654
- }
655
 
656
- /**
657
- * Hidden input field.
658
- *
659
- * @param array $option
660
- * @param array $attributes
661
- * @param mixed $value
662
- * @param string $ignored_placeholder
663
- */
664
- protected function input_hidden( $option, $attributes, $value, $ignored_placeholder ) {
665
- $human_value = $value;
666
- if ( $option['human_value'] ) {
667
- $human_value = $option['human_value'];
668
- }
669
- ?>
670
- <input
671
- id="setting-<?php echo esc_attr( $option['name'] ); ?>"
672
- type="hidden"
673
- name="<?php echo esc_attr( $option['name'] ); ?>"
674
- value="<?php echo esc_attr( $value ); ?>"
675
- <?php
676
- echo implode( ' ', $attributes ); // WPCS: XSS ok.
677
- ?>
678
- /><strong><?php echo esc_html( $human_value ); ?></strong>
679
- <?php
680
 
681
- if ( ! empty( $option['desc'] ) ) {
682
- echo ' <p class="description">' . wp_kses_post( $option['desc'] ) . '</p>';
683
- }
684
- }
 
 
 
 
 
685
 
686
- /**
687
- * Password input field.
688
- *
689
- * @param array $option
690
- * @param array $attributes
691
- * @param mixed $value
692
- * @param string $placeholder
693
- */
694
- protected function input_password( $option, $attributes, $value, $placeholder ) {
695
- ?>
696
- <input
697
- id="setting-<?php echo esc_attr( $option['name'] ); ?>"
698
- class="regular-text"
699
- type="password"
700
- name="<?php echo esc_attr( $option['name'] ); ?>"
701
- value="<?php echo esc_attr( $value ); ?>"
702
- <?php
703
- echo implode( ' ', $attributes ) . ' '; // WPCS: XSS ok.
704
- echo $placeholder; // WPCS: XSS ok.
705
- ?>
706
- />
707
- <?php
708
 
709
- if ( ! empty( $option['desc'] ) ) {
710
- echo ' <p class="description">' . wp_kses_post( $option['desc'] ) . '</p>';
711
- }
712
- }
713
 
714
- /**
715
- * Number input field.
716
- *
717
- * @param array $option
718
- * @param array $attributes
719
- * @param mixed $value
720
- * @param string $placeholder
721
- */
722
- protected function input_number( $option, $attributes, $value, $placeholder ) {
723
- echo isset( $option['before'] ) ? wp_kses_post( $option['before'] ) : '';
724
- ?>
725
- <input
726
- id="setting-<?php echo esc_attr( $option['name'] ); ?>"
727
- class="small-text"
728
- type="number"
729
- name="<?php echo esc_attr( $option['name'] ); ?>"
730
- value="<?php echo esc_attr( $value ); ?>"
731
- <?php
732
- echo implode( ' ', $attributes ) . ' '; // WPCS: XSS ok.
733
- echo $placeholder; // WPCS: XSS ok.
734
- ?>
735
- />
736
- <?php
737
- echo isset( $option['after'] ) ? wp_kses_post( $option['after'] ) : '';
738
- if ( ! empty( $option['desc'] ) ) {
739
- echo ' <p class="description">' . wp_kses_post( $option['desc'] ) . '</p>';
740
- }
741
- }
742
 
743
- /**
744
- * Text input field.
745
- *
746
- * @param array $option
747
- * @param array $attributes
748
- * @param mixed $value
749
- * @param string $placeholder
750
- */
751
- protected function input_text( $option, $attributes, $value, $placeholder ) {
752
- ?>
753
- <input
754
- id="setting-<?php echo esc_attr( $option['name'] ); ?>"
755
- class="regular-text"
756
- type="text"
757
- name="<?php echo esc_attr( $option['name'] ); ?>"
758
- value="<?php echo esc_attr( $value ); ?>"
759
- <?php
760
- echo implode( ' ', $attributes ) . ' '; // WPCS: XSS ok.
761
- echo $placeholder; // WPCS: XSS ok.
762
- ?>
763
- />
764
- <?php
765
 
766
- if ( ! empty( $option['desc'] ) ) {
767
- echo ' <p class="description">' . wp_kses_post( $option['desc'] ) . '</p>';
768
- }
769
- }
770
 
771
- /**
772
- * Outputs the field row.
773
- *
774
- * @param array $option
775
- * @param mixed $value
776
- */
777
- protected function output_field( $option, $value ) {
778
- $placeholder = ( ! empty( $option['placeholder'] ) ) ? 'placeholder="' . esc_attr( $option['placeholder'] ) . '"' : '';
779
- $class = ! empty( $option['class'] ) ? $option['class'] : '';
780
- $option['type'] = ! empty( $option['type'] ) ? $option['type'] : 'text';
781
- $attributes = array();
782
- if ( ! empty( $option['attributes'] ) && is_array( $option['attributes'] ) ) {
783
- foreach ( $option['attributes'] as $attribute_name => $attribute_value ) {
784
- $attributes[] = esc_attr( $attribute_name ) . '="' . esc_attr( $attribute_value ) . '"';
785
- }
786
- }
787
 
788
- echo '<tr valign="top" class="' . esc_attr( $class ) . '">';
 
 
 
 
 
 
 
789
 
790
- if ( ! empty( $option['label'] ) ) {
791
- echo '<th scope="row"><label for="setting-' . esc_attr( $option['name'] ) . '">' . esc_html( $option['label'] ) . '</a></th><td>';
792
- } else {
793
- echo '<td colspan="2">';
794
- }
 
 
795
 
796
- $method_name = 'input_' . $option['type'];
797
- if ( method_exists( $this, $method_name ) ) {
798
- $this->$method_name( $option, $attributes, $value, $placeholder );
799
- } else {
800
- /**
801
- * Allows for custom fields in admin setting panes.
802
- *
803
- * @since 1.14.0
804
- *
805
- * @param string $option Field name.
806
- * @param array $attributes Array of attributes.
807
- * @param mixed $value Field value.
808
- * @param string $value Placeholder text.
809
- */
810
- do_action( 'wp_job_manager_admin_field_' . $option['type'], $option, $attributes, $value, $placeholder );
811
- }
812
- echo '</td></tr>';
813
- }
814
 
815
- /**
816
- * Multiple settings stored in one setting array that are shown when the `enable` setting is checked.
817
- *
818
- * @param array $option
819
- * @param array $attributes
820
- * @param array $values
821
- * @param string $placeholder
822
- */
823
- protected function input_multi_enable_expand( $option, $attributes, $values, $placeholder ) {
824
- echo '<div class="setting-enable-expand">';
825
- $enable_option = $option['enable_field'];
826
- $enable_option['name'] = $option['name'] . '[' . $enable_option['name'] . ']';
827
- $enable_option['type'] = 'checkbox';
828
- $enable_option['attributes'] = array( 'class="sub-settings-expander"' );
829
- $this->input_checkbox( $enable_option, $enable_option['attributes'], $values[ $option['enable_field']['name'] ], null );
830
-
831
- echo '<div class="sub-settings-expandable">';
832
- $this->input_multi( $option, $attributes, $values, $placeholder );
833
- echo '</div>';
834
- echo '</div>';
835
- }
836
 
837
- /**
838
- * Multiple settings stored in one setting array.
839
- *
840
- * @param array $option
841
- * @param array $ignored_attributes
842
- * @param array $values
843
- * @param string $ignored_placeholder
844
- */
845
- protected function input_multi( $option, $ignored_attributes, $values, $ignored_placeholder ) {
846
- echo '<table class="form-table settings child-settings">';
847
- foreach ( $option['settings'] as $sub_option ) {
848
- $value = isset( $values[ $sub_option['name'] ] ) ? $values[ $sub_option['name'] ] : $sub_option['std'];
849
- $sub_option['name'] = $option['name'] . '[' . $sub_option['name'] . ']';
850
- $this->output_field( $sub_option, $value );
851
- }
852
- echo '</table>';
853
- }
854
 
855
- /**
856
- * Proxy for text input field.
857
- *
858
- * @param array $option
859
- * @param array $attributes
860
- * @param mixed $value
861
- * @param string $placeholder
862
- */
863
- protected function input_input( $option, $attributes, $value, $placeholder ) {
864
- $this->input_text( $option, $attributes, $value, $placeholder );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
865
  }
866
  }
1
  <?php
2
+
3
+ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
4
 
5
  /**
6
+ * WP_Job_Manager_Settings class.
 
 
 
7
  */
8
  class WP_Job_Manager_Settings {
9
 
10
  /**
11
+ * __construct function.
 
 
 
 
 
 
 
 
12
  *
13
+ * @access public
14
+ * @return void
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  */
16
  public function __construct() {
17
  $this->settings_group = 'job_manager';
19
  }
20
 
21
  /**
22
+ * init_settings function.
 
 
 
 
 
 
 
 
 
 
 
 
23
  *
24
  * @access protected
25
+ * @return void
26
  */
27
  protected function init_settings() {
28
+ // Prepare roles option
29
  $roles = get_editable_roles();
30
  $account_roles = array();
31
 
32
  foreach ( $roles as $key => $role ) {
33
+ if ( $key == 'administrator' ) {
34
  continue;
35
  }
36
  $account_roles[ $key ] = $role['name'];
37
  }
38
 
39
+ $this->settings = apply_filters( 'job_manager_settings',
 
40
  array(
41
+ 'job_listings' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  __( 'Job Listings', 'wp-job-manager' ),
43
  array(
44
  array(
46
  'std' => '10',
47
  'placeholder' => '',
48
  'label' => __( 'Listings Per Page', 'wp-job-manager' ),
49
+ 'desc' => __( 'How many listings should be shown per page by default?', 'wp-job-manager' ),
50
+ 'attributes' => array()
51
  ),
52
  array(
53
  'name' => 'job_manager_hide_filled_positions',
54
  'std' => '0',
55
  'label' => __( 'Filled Positions', 'wp-job-manager' ),
56
  'cb_label' => __( 'Hide filled positions', 'wp-job-manager' ),
57
+ 'desc' => __( 'If enabled, filled positions will be hidden from archives.', 'wp-job-manager' ),
 
 
 
 
 
 
 
 
 
58
  'type' => 'checkbox',
59
+ 'attributes' => array()
60
  ),
61
  array(
62
  'name' => 'job_manager_hide_expired_content',
63
  'std' => '1',
64
+ 'label' => __( 'Expired Listings', 'wp-job-manager' ),
65
+ 'cb_label' => __( 'Hide content within expired listings', 'wp-job-manager' ),
66
+ 'desc' => __( 'If enabled, the content within expired listings will be hidden. Otherwise, expired listings will be displayed as normal (without the application area).', 'wp-job-manager' ),
67
  'type' => 'checkbox',
68
+ 'attributes' => array()
69
  ),
70
  array(
71
  'name' => 'job_manager_enable_categories',
72
  'std' => '0',
73
  'label' => __( 'Categories', 'wp-job-manager' ),
74
+ 'cb_label' => __( 'Enable categories for listings', 'wp-job-manager' ),
75
+ 'desc' => __( 'Choose whether to enable categories. Categories must be setup by an admin to allow users to choose them during submission.', 'wp-job-manager' ),
76
  'type' => 'checkbox',
77
+ 'attributes' => array()
78
  ),
79
  array(
80
  'name' => 'job_manager_enable_default_category_multiselect',
81
  'std' => '0',
82
  'label' => __( 'Multi-select Categories', 'wp-job-manager' ),
83
+ 'cb_label' => __( 'Enable category multiselect by default', 'wp-job-manager' ),
84
+ 'desc' => __( 'If enabled, the category select box will default to a multiselect on the [jobs] shortcode.', 'wp-job-manager' ),
85
  'type' => 'checkbox',
86
+ 'attributes' => array()
87
  ),
88
  array(
89
+ 'name' => 'job_manager_category_filter_type',
90
+ 'std' => 'any',
91
+ 'label' => __( 'Category Filter Type', 'wp-job-manager' ),
92
+ 'desc' => __( 'If enabled, the category select box will default to a multiselect on the [jobs] shortcode.', 'wp-job-manager' ),
93
+ 'type' => 'select',
94
  'options' => array(
95
+ 'any' => __( 'Jobs will be shown if within ANY selected category', 'wp-job-manager' ),
96
  'all' => __( 'Jobs will be shown if within ALL selected categories', 'wp-job-manager' ),
97
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  ),
99
  ),
100
  ),
105
  'name' => 'job_manager_user_requires_account',
106
  'std' => '1',
107
  'label' => __( 'Account Required', 'wp-job-manager' ),
108
+ 'cb_label' => __( 'Submitting listings requires an account', 'wp-job-manager' ),
109
+ 'desc' => __( 'If disabled, non-logged in users will be able to submit listings without creating an account.', 'wp-job-manager' ),
110
  'type' => 'checkbox',
111
+ 'attributes' => array()
112
  ),
113
  array(
114
  'name' => 'job_manager_enable_registration',
115
  'std' => '1',
116
  'label' => __( 'Account Creation', 'wp-job-manager' ),
117
+ 'cb_label' => __( 'Allow account creation', 'wp-job-manager' ),
118
+ 'desc' => __( 'If enabled, non-logged in users will be able to create an account by entering their email address on the submission form.', 'wp-job-manager' ),
119
  'type' => 'checkbox',
120
+ 'attributes' => array()
121
  ),
122
  array(
123
  'name' => 'job_manager_generate_username_from_email',
124
  'std' => '1',
125
  'label' => __( 'Account Username', 'wp-job-manager' ),
126
+ 'cb_label' => __( 'Automatically Generate Username from Email Address', 'wp-job-manager' ),
127
+ 'desc' => __( 'If enabled, a username will be generated from the first part of the user email address. Otherwise, a username field will be shown.', 'wp-job-manager' ),
128
  'type' => 'checkbox',
129
+ 'attributes' => array()
130
  ),
131
  array(
132
+ 'name' => 'job_manager_registration_role',
133
+ 'std' => 'employer',
134
+ 'label' => __( 'Account Role', 'wp-job-manager' ),
135
+ 'desc' => __( 'If you enable registration on your submission form, choose a role for the new user.', 'wp-job-manager' ),
136
+ 'type' => 'select',
137
+ 'options' => $account_roles
 
 
 
 
 
 
 
 
 
138
  ),
139
  array(
140
  'name' => 'job_manager_submission_requires_approval',
141
  'std' => '1',
142
  'label' => __( 'Moderate New Listings', 'wp-job-manager' ),
143
+ 'cb_label' => __( 'New listing submissions require admin approval', 'wp-job-manager' ),
144
+ 'desc' => __( 'If enabled, new submissions will be inactive, pending admin approval.', 'wp-job-manager' ),
145
  'type' => 'checkbox',
146
+ 'attributes' => array()
147
  ),
148
  array(
149
  'name' => 'job_manager_user_can_edit_pending_submissions',
150
  'std' => '0',
151
  'label' => __( 'Allow Pending Edits', 'wp-job-manager' ),
152
+ 'cb_label' => __( 'Submissions awaiting approval can be edited', 'wp-job-manager' ),
153
+ 'desc' => __( 'If enabled, submissions awaiting admin approval can be edited by the user.', 'wp-job-manager' ),
154
  'type' => 'checkbox',
155
+ 'attributes' => array()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  ),
157
  array(
158
  'name' => 'job_manager_submission_duration',
159
  'std' => '30',
160
  'label' => __( 'Listing Duration', 'wp-job-manager' ),
161
+ 'desc' => __( 'How many <strong>days</strong> listings are live before expiring. Can be left blank to never expire.', 'wp-job-manager' ),
162
+ 'attributes' => array()
163
  ),
164
  array(
165
+ 'name' => 'job_manager_allowed_application_method',
166
+ 'std' => '',
167
+ 'label' => __( 'Application Method', 'wp-job-manager' ),
168
+ 'desc' => __( 'Choose the contact method for listings.', 'wp-job-manager' ),
169
+ 'type' => 'select',
170
+ 'options' => array(
171
  '' => __( 'Email address or website URL', 'wp-job-manager' ),
172
  'email' => __( 'Email addresses only', 'wp-job-manager' ),
173
  'url' => __( 'Website URLs only', 'wp-job-manager' ),
174
+ )
175
+ )
176
+ )
177
  ),
178
+ 'job_pages' => array(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  __( 'Pages', 'wp-job-manager' ),
180
  array(
181
  array(
182
+ 'name' => 'job_manager_submit_job_form_page_id',
183
+ 'std' => '',
184
+ 'label' => __( 'Submit Job Form Page', 'wp-job-manager' ),
185
+ 'desc' => __( 'Select the page where you have placed the [submit_job_form] shortcode. This lets the plugin know where the form is located.', 'wp-job-manager' ),
186
+ 'type' => 'page'
187
  ),
188
  array(
189
+ 'name' => 'job_manager_job_dashboard_page_id',
190
+ 'std' => '',
191
+ 'label' => __( 'Job Dashboard Page', 'wp-job-manager' ),
192
+ 'desc' => __( 'Select the page where you have placed the [job_dashboard] shortcode. This lets the plugin know where the dashboard is located.', 'wp-job-manager' ),
193
+ 'type' => 'page'
194
  ),
195
  array(
196
+ 'name' => 'job_manager_jobs_page_id',
197
+ 'std' => '',
198
+ 'label' => __( 'Job Listings Page', 'wp-job-manager' ),
199
+ 'desc' => __( 'Select the page where you have placed the [jobs] shortcode. This lets the plugin know where the job listings page is located.', 'wp-job-manager' ),
200
+ 'type' => 'page'
201
  ),
202
+ )
203
+ )
204
  )
205
  );
206
  }
207
 
208
  /**
209
+ * register_settings function.
210
+ *
211
+ * @access public
212
+ * @return void
213
  */
214
  public function register_settings() {
215
  $this->init_settings();
216
 
217
  foreach ( $this->settings as $section ) {
218
  foreach ( $section[1] as $option ) {
219
+ if ( isset( $option['std'] ) )
220
  add_option( $option['name'], $option['std'] );
 
221
  register_setting( $this->settings_group, $option['name'] );
222
  }
223
  }
224
  }
225
 
226
  /**
227
+ * output function.
228
+ *
229
+ * @access public
230
+ * @return void
231
  */
232
  public function output() {
233
  $this->init_settings();
234
  ?>
235
  <div class="wrap job-manager-settings-wrap">
236
+ <form method="post" action="options.php">
237
 
238
  <?php settings_fields( $this->settings_group ); ?>
239
 
240
+ <h2 class="nav-tab-wrapper">
241
+ <?php
242
+ foreach ( $this->settings as $key => $section ) {
243
+ echo '<a href="#settings-' . sanitize_title( $key ) . '" class="nav-tab">' . esc_html( $section[0] ) . '</a>';
244
+ }
245
+ ?>
246
+ </h2>
247
 
248
  <?php
249
+ if ( ! empty( $_GET['settings-updated'] ) ) {
250
+ flush_rewrite_rules();
251
+ echo '<div class="updated fade job-manager-updated"><p>' . __( 'Settings successfully saved', 'wp-job-manager' ) . '</p></div>';
 
 
 
 
 
 
 
252
  }
 
253
 
254
+ foreach ( $this->settings as $key => $section ) {
 
 
 
255
 
256
+ echo '<div id="settings-' . sanitize_title( $key ) . '" class="settings_panel">';
 
 
 
 
257
 
258
+ echo '<table class="form-table">';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
+ foreach ( $section[1] as $option ) {
 
 
 
 
 
 
 
 
 
 
261
 
262
+ $placeholder = ( ! empty( $option['placeholder'] ) ) ? 'placeholder="' . $option['placeholder'] . '"' : '';
263
+ $class = ! empty( $option['class'] ) ? $option['class'] : '';
264
+ $value = get_option( $option['name'] );
265
+ $option['type'] = ! empty( $option['type'] ) ? $option['type'] : '';
266
+ $attributes = array();
267
 
268
+ if ( ! empty( $option['attributes'] ) && is_array( $option['attributes'] ) )
269
+ foreach ( $option['attributes'] as $attribute_name => $attribute_value )
270
+ $attributes[] = esc_attr( $attribute_name ) . '="' . esc_attr( $attribute_value ) . '"';
 
 
 
 
 
 
 
271
 
272
+ echo '<tr valign="top" class="' . $class . '"><th scope="row"><label for="setting-' . $option['name'] . '">' . $option['label'] . '</a></th><td>';
 
 
 
 
 
 
 
 
 
 
 
273
 
274
+ switch ( $option['type'] ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
 
276
+ case "checkbox" :
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
+ ?><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
 
 
 
279
 
280
+ if ( $option['desc'] )
281
+ echo ' <p class="description">' . $option['desc'] . '</p>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
 
283
+ break;
284
+ case "textarea" :
 
 
285
 
286
+ ?><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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
+ if ( $option['desc'] )
289
+ echo ' <p class="description">' . $option['desc'] . '</p>';
 
 
 
 
 
290
 
291
+ break;
292
+ case "select" :
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
 
294
+ ?><select id="setting-<?php echo $option['name']; ?>" class="regular-text" name="<?php echo $option['name']; ?>" <?php echo implode( ' ', $attributes ); ?>><?php
295
+ foreach( $option['options'] as $key => $name )
296
+ echo '<option value="' . esc_attr( $key ) . '" ' . selected( $value, $key, false ) . '>' . esc_html( $name ) . '</option>';
297
+ ?></select><?php
298
 
299
+ if ( $option['desc'] ) {
300
+ echo ' <p class="description">' . $option['desc'] . '</p>';
301
+ }
 
302
 
303
+ break;
304
+ case "page" :
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
+ $args = array(
307
+ 'name' => $option['name'],
308
+ 'id' => $option['name'],
309
+ 'sort_column' => 'menu_order',
310
+ 'sort_order' => 'ASC',
311
+ 'show_option_none' => __( '--no page--', 'wp-job-manager' ),
312
+ 'echo' => false,
313
+ 'selected' => absint( $value )
314
+ );
315
 
316
+ echo str_replace(' id=', " data-placeholder='" . __( 'Select a page&hellip;', 'wp-job-manager' ) . "' id=", wp_dropdown_pages( $args ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
 
318
+ if ( $option['desc'] ) {
319
+ echo ' <p class="description">' . $option['desc'] . '</p>';
320
+ }
 
321
 
322
+ break;
323
+ case "password" :
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
 
325
+ ?><input id="setting-<?php echo $option['name']; ?>" class="regular-text" type="password" name="<?php echo $option['name']; ?>" value="<?php esc_attr_e( $value ); ?>" <?php echo implode( ' ', $attributes ); ?> <?php echo $placeholder; ?> /><?php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
 
327
+ if ( $option['desc'] ) {
328
+ echo ' <p class="description">' . $option['desc'] . '</p>';
329
+ }
 
330
 
331
+ break;
332
+ case "number" :
333
+ ?><input id="setting-<?php echo $option['name']; ?>" class="regular-text" type="number" name="<?php echo $option['name']; ?>" value="<?php esc_attr_e( $value ); ?>" <?php echo implode( ' ', $attributes ); ?> <?php echo $placeholder; ?> /><?php
 
 
 
 
 
 
 
 
 
 
 
 
 
334
 
335
+ if ( $option['desc'] ) {
336
+ echo ' <p class="description">' . $option['desc'] . '</p>';
337
+ }
338
+ break;
339
+ case "" :
340
+ case "input" :
341
+ case "text" :
342
+ ?><input id="setting-<?php echo $option['name']; ?>" class="regular-text" type="text" name="<?php echo $option['name']; ?>" value="<?php esc_attr_e( $value ); ?>" <?php echo implode( ' ', $attributes ); ?> <?php echo $placeholder; ?> /><?php
343
 
344
+ if ( $option['desc'] ) {
345
+ echo ' <p class="description">' . $option['desc'] . '</p>';
346
+ }
347
+ break;
348
+ default :
349
+ do_action( 'wp_job_manager_admin_field_' . $option['type'], $option, $attributes, $value, $placeholder );
350
+ break;
351
 
352
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
 
354
+ echo '</td></tr>';
355
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
 
357
+ echo '</table></div>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
 
359
+ }
360
+ ?>
361
+ <p class="submit">
362
+ <input type="submit" class="button-primary" value="<?php _e( 'Save Changes', 'wp-job-manager' ); ?>" />
363
+ </p>
364
+ </form>
365
+ </div>
366
+ <script type="text/javascript">
367
+ jQuery('.nav-tab-wrapper a').click(function() {
368
+ jQuery('.settings_panel').hide();
369
+ jQuery('.nav-tab-active').removeClass('nav-tab-active');
370
+ jQuery( jQuery(this).attr('href') ).show();
371
+ jQuery(this).addClass('nav-tab-active');
372
+ return false;
373
+ });
374
+ jQuery('.nav-tab-wrapper a:first').click();
375
+ jQuery('#setting-job_manager_enable_registration').change(function(){
376
+ if ( jQuery( this ).is(':checked') ) {
377
+ jQuery('#setting-job_manager_registration_role').closest('tr').show();
378
+ jQuery('#setting-job_manager_registration_username_from_email').closest('tr').show();
379
+ } else {
380
+ jQuery('#setting-job_manager_registration_role').closest('tr').hide();
381
+ jQuery('#setting-job_manager_registration_username_from_email').closest('tr').hide();
382
+ }
383
+ }).change();
384
+ </script>
385
+ <?php
386
  }
387
  }
includes/admin/class-wp-job-manager-setup.php CHANGED
@@ -4,83 +4,65 @@ if ( ! defined( 'ABSPATH' ) ) {
4
  }
5
 
6
  /**
7
- * Handles initial environment setup after plugin is first activated.
8
- *
9
- * @package wp-job-manager
10
- * @since 1.16.0
11
  */
12
  class WP_Job_Manager_Setup {
13
 
14
  /**
15
- * The single instance of the class.
16
  *
17
- * @var self
18
- * @since 1.26.0
19
- */
20
- private static $_instance = null;
21
-
22
- /**
23
- * Allows for accessing single instance of class. Class should only be constructed once per call.
24
- *
25
- * @since 1.26.0
26
- * @static
27
- * @return self Main instance.
28
- */
29
- public static function instance() {
30
- if ( is_null( self::$_instance ) ) {
31
- self::$_instance = new self();
32
- }
33
- return self::$_instance;
34
- }
35
-
36
- /**
37
- * Constructor.
38
  */
39
  public function __construct() {
40
  add_action( 'admin_menu', array( $this, 'admin_menu' ), 12 );
41
  add_action( 'admin_head', array( $this, 'admin_head' ) );
42
  add_action( 'admin_init', array( $this, 'redirect' ) );
43
- if ( isset( $_GET['page'] ) && 'job-manager-setup' === $_GET['page'] ) {
44
- add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ), 12 );
45
- }
46
  }
47
 
48
  /**
49
- * Adds setup link to admin dashboard menu briefly so the page callback is registered.
 
 
 
50
  */
51
  public function admin_menu() {
52
- add_dashboard_page( __( 'Setup', 'wp-job-manager' ), __( 'Setup', 'wp-job-manager' ), 'manage_options', 'job-manager-setup', array( $this, 'setup_page' ) );
53
  }
54
 
55
  /**
56
- * Removes the setup link from admin dashboard menu so just the handler callback is registered.
 
 
 
57
  */
58
  public function admin_head() {
59
  remove_submenu_page( 'index.php', 'job-manager-setup' );
60
  }
61
 
62
  /**
63
- * Sends user to the setup page on first activation.
64
  */
65
  public function redirect() {
66
- // Bail if no activation redirect transient is set.
67
- if ( ! get_transient( '_job_manager_activation_redirect' ) ) {
68
  return;
69
- }
70
 
71
- if ( ! current_user_can( 'manage_options' ) ) {
72
- return;
73
- }
74
 
75
- // Delete the redirect transient.
76
  delete_transient( '_job_manager_activation_redirect' );
77
 
78
- // Bail if activating from network, or bulk, or within an iFrame.
79
  if ( is_network_admin() || isset( $_GET['activate-multi'] ) || defined( 'IFRAME_REQUEST' ) ) {
80
  return;
81
  }
82
 
83
- if ( ( isset( $_GET['action'] ) && 'upgrade-plugin' === $_GET['action'] ) && ( isset( $_GET['plugin'] ) && strstr( $_GET['plugin'], 'wp-job-manager.php' ) ) ) {
84
  return;
85
  }
86
 
@@ -89,15 +71,14 @@ class WP_Job_Manager_Setup {
89
  }
90
 
91
  /**
92
- * Enqueues scripts for setup page.
93
  */
94
  public function admin_enqueue_scripts() {
95
- wp_enqueue_style( 'job_manager_setup_css', JOB_MANAGER_PLUGIN_URL . '/assets/css/setup.css', array( 'dashicons' ), JOB_MANAGER_VERSION );
96
  }
97
 
98
  /**
99
- * Creates a page.
100
- *
101
  * @param string $title
102
  * @param string $content
103
  * @param string $option
@@ -111,9 +92,9 @@ class WP_Job_Manager_Setup {
111
  'post_title' => $title,
112
  'post_content' => $content,
113
  'post_parent' => 0,
114
- 'comment_status' => 'closed',
115
  );
116
- $page_id = wp_insert_post( $page_data );
117
 
118
  if ( $option ) {
119
  update_option( $option, $page_id );
@@ -121,76 +102,18 @@ class WP_Job_Manager_Setup {
121
  }
122
 
123
  /**
124
- * Handle request to the setup page.
125
- */
126
- public function setup_page() {
127
- $usage_tracking = WP_Job_Manager_Usage_Tracking::get_instance();
128
-
129
- if ( 'POST' === $_SERVER['REQUEST_METHOD'] ) {
130
- $enable = isset( $_POST['job_manager_usage_tracking_enabled'] )
131
- && '1' === $_POST['job_manager_usage_tracking_enabled'];
132
-
133
- $nonce = isset( $_POST['nonce'] ) ? $_POST['nonce'] : null;
134
- $valid_nonce = wp_verify_nonce( $nonce, 'enable-usage-tracking' );
135
-
136
- if ( $valid_nonce ) {
137
- $usage_tracking->set_tracking_enabled( $enable );
138
- $usage_tracking->hide_tracking_opt_in();
139
- }
140
- }
141
-
142
- $this->output();
143
- }
144
-
145
- /**
146
- * Usage tracking opt in text for setup page.
147
- */
148
- private function opt_in_text() {
149
- return WP_Job_Manager_Usage_Tracking::get_instance()->opt_in_checkbox_text();
150
- }
151
-
152
- /**
153
- * Output opt-in checkbox if usage tracking isn't already enabled.
154
- */
155
- private function maybe_output_opt_in_checkbox() {
156
- // Only show the checkbox if we aren't already opted in.
157
- $usage_tracking = WP_Job_Manager_Usage_Tracking::get_instance();
158
- if ( ! $usage_tracking->get_tracking_enabled() ) {
159
- ?>
160
- <p>
161
- <label>
162
- <input
163
- type="checkbox"
164
- name="job_manager_usage_tracking_enabled"
165
- value="1" />
166
- <?php
167
- echo wp_kses(
168
- $this->opt_in_text(),
169
- $usage_tracking->opt_in_dialog_text_allowed_html()
170
- );
171
- ?>
172
- </label>
173
- </p>
174
- <?php
175
- }
176
- }
177
-
178
- /**
179
- * Displays setup page.
180
  */
181
  public function output() {
182
  $step = ! empty( $_GET['step'] ) ? absint( $_GET['step'] ) : 1;
183
 
184
  if ( 3 === $step && ! empty( $_POST ) ) {
185
- if ( false === wp_verify_nonce( $_REQUEST['setup_wizard'], 'step_3' ) ) {
186
- wp_die( 'Error in nonce. Try again.', 'wp-job-manager' );
187
- }
188
  $create_pages = isset( $_POST['wp-job-manager-create-page'] ) ? $_POST['wp-job-manager-create-page'] : array();
189
  $page_titles = $_POST['wp-job-manager-page-title'];
190
  $pages_to_create = array(
191
  'submit_job_form' => '[submit_job_form]',
192
  'job_dashboard' => '[job_dashboard]',
193
- 'jobs' => '[jobs]',
194
  );
195
 
196
  foreach ( $pages_to_create as $page => $content ) {
@@ -202,70 +125,42 @@ class WP_Job_Manager_Setup {
202
  }
203
  ?>
204
  <div class="wrap wp_job_manager wp_job_manager_addons_wrap">
205
- <h2><?php esc_html_e( 'WP Job Manager Setup', 'wp-job-manager' ); ?></h2>
206
 
207
  <ul class="wp-job-manager-setup-steps">
208
- <?php
209
- $step_classes = array_fill( 1, 3, '' );
210
- $step_classes[ $step ] = 'wp-job-manager-setup-active-step';
211
- ?>
212
- <li class="<?php echo sanitize_html_class( $step_classes[1] ); ?>"><?php esc_html_e( '1. Introduction', 'wp-job-manager' ); ?></li>
213
- <li class="<?php echo sanitize_html_class( $step_classes[2] ); ?>"><?php esc_html_e( '2. Page Setup', 'wp-job-manager' ); ?></li>
214
- <li class="<?php echo sanitize_html_class( $step_classes[3] ); ?>"><?php esc_html_e( '3. Done', 'wp-job-manager' ); ?></li>
215
  </ul>
216
 
217
  <?php if ( 1 === $step ) : ?>
218
 
219
- <h3><?php esc_html_e( 'Welcome to the Setup Wizard!', 'wp-job-manager' ); ?></h3>
220
-
221
- <p><?php echo wp_kses_post( __( 'Thanks for installing <em>WP Job Manager</em>! Let\'s get your site ready to accept job listings.', 'wp-job-manager' ) ); ?></p>
222
- <p><?php echo wp_kses_post( __( 'This setup wizard will walk you through the process of creating pages for job submissions, management, and listings.', 'wp-job-manager' ) ); ?></p>
223
- <p>
224
- <?php
225
- // translators: Placeholder %s is the path to WPJM documentation site.
226
- echo wp_kses_post( sprintf( __( 'If you\'d prefer to skip this and set up your pages manually, our <a href="%s">documentation</a> will walk you through each step.', 'wp-job-manager' ), 'https://wpjobmanager.com/documentation/' ) );
227
- ?>
228
- </p>
229
-
230
- <form method="post" action="<?php echo esc_url( add_query_arg( 'step', 2 ) ); ?>">
231
- <input type="hidden" name="nonce" value="<?php echo esc_attr( wp_create_nonce( 'enable-usage-tracking' ) ); ?>" />
232
 
233
- <?php $this->maybe_output_opt_in_checkbox(); ?>
 
 
234
 
235
- <p class="submit">
236
- <input type="submit" value="<?php esc_html_e( 'Start setup', 'wp-job-manager' ); ?>" class="button button-primary" />
237
- <a href="<?php echo esc_url( add_query_arg( 'skip-job-manager-setup', 1, admin_url( 'index.php?page=job-manager-setup&step=3' ) ) ); ?>" class="button"><?php esc_html_e( 'Skip setup. I will set up the plugin manually.', 'wp-job-manager' ); ?></a>
238
- </p>
239
- </form>
240
 
241
  <?php endif; ?>
242
  <?php if ( 2 === $step ) : ?>
243
 
244
- <h3><?php esc_html_e( 'Page Setup', 'wp-job-manager' ); ?></h3>
245
-
246
- <p><?php esc_html_e( 'With WP Job Manager, employers and applicants can post, manage, and browse job listings right on your website. Tell us which of these common pages you\'d like your site to have and we\'ll create and configure them for you.', 'wp-job-manager' ); ?></p>
247
- <p>
248
- <?php
249
- echo wp_kses_post( sprintf(
250
- // translators: %1$s is URL to WordPress core shortcode documentation. %2$s is URL to WPJM specific shortcode reference.
251
- __( '(These pages are created using <a href="%1$s" title="What is a shortcode?" target="_blank" class="help-page-link">shortcodes</a>,
252
- which we take care of in this step. If you\'d like to build these pages yourself or want to add one of these options to an existing
253
- page on your site, you can skip this step and take a look at <a href="%2$s" target="_blank" class="help-page-link">shortcode documentation</a> for detailed instructions.)', 'wp-job-manager' ),
254
- 'http://codex.wordpress.org/Shortcode',
255
- 'https://wpjobmanager.com/document/shortcode-reference/'
256
- ) );
257
- ?>
258
- </p>
259
 
260
  <form action="<?php echo esc_url( add_query_arg( 'step', 3 ) ); ?>" method="post">
261
- <?php wp_nonce_field( 'step_3', 'setup_wizard' ); ?>
262
  <table class="wp-job-manager-shortcodes widefat">
263
  <thead>
264
  <tr>
265
  <th>&nbsp;</th>
266
- <th><?php esc_html_e( 'Page Title', 'wp-job-manager' ); ?></th>
267
- <th><?php esc_html_e( 'Page Description', 'wp-job-manager' ); ?></th>
268
- <th><?php esc_html_e( 'Content Shortcode', 'wp-job-manager' ); ?></th>
269
  </tr>
270
  </thead>
271
  <tbody>
@@ -273,7 +168,9 @@ class WP_Job_Manager_Setup {
273
  <td><input type="checkbox" checked="checked" name="wp-job-manager-create-page[submit_job_form]" /></td>
274
  <td><input type="text" value="<?php echo esc_attr( _x( 'Post a Job', 'Default page title (wizard)', 'wp-job-manager' ) ); ?>" name="wp-job-manager-page-title[submit_job_form]" /></td>
275
  <td>
276
- <p><?php esc_html_e( 'Creates a page that allows employers to post new jobs directly from a page on your website, instead of requiring them to log in to an admin area. If you\'d rather not allow this -- for example, if you want employers to use the admin dashboard only -- you can uncheck this setting.', 'wp-job-manager' ); ?></p>
 
 
277
  </td>
278
  <td><code>[submit_job_form]</code></td>
279
  </tr>
@@ -281,14 +178,16 @@ class WP_Job_Manager_Setup {
281
  <td><input type="checkbox" checked="checked" name="wp-job-manager-create-page[job_dashboard]" /></td>
282
  <td><input type="text" value="<?php echo esc_attr( _x( 'Job Dashboard', 'Default page title (wizard)', 'wp-job-manager' ) ); ?>" name="wp-job-manager-page-title[job_dashboard]" /></td>
283
  <td>
284
- <p><?php esc_html_e( 'Creates a page that allows employers to manage their job listings directly from a page on your website, instead of requiring them to log in to an admin area. If you want to manage all job listings from the admin dashboard only, you can uncheck this setting.', 'wp-job-manager' ); ?></p>
 
 
285
  </td>
286
  <td><code>[job_dashboard]</code></td>
287
  </tr>
288
  <tr>
289
  <td><input type="checkbox" checked="checked" name="wp-job-manager-create-page[jobs]" /></td>
290
  <td><input type="text" value="<?php echo esc_attr( _x( 'Jobs', 'Default page title (wizard)', 'wp-job-manager' ) ); ?>" name="wp-job-manager-page-title[jobs]" /></td>
291
- <td><?php esc_html_e( 'Creates a page where visitors can browse, search, and filter job listings.', 'wp-job-manager' ); ?></td>
292
  <td><code>[jobs]</code></td>
293
  </tr>
294
  </tbody>
@@ -296,7 +195,7 @@ class WP_Job_Manager_Setup {
296
  <tr>
297
  <th colspan="4">
298
  <input type="submit" class="button button-primary" value="Create selected pages" />
299
- <a href="<?php echo esc_url( add_query_arg( 'step', 3 ) ); ?>" class="button"><?php esc_html_e( 'Skip this step', 'wp-job-manager' ); ?></a>
300
  </th>
301
  </tr>
302
  </tfoot>
@@ -306,64 +205,43 @@ class WP_Job_Manager_Setup {
306
  <?php endif; ?>
307
  <?php if ( 3 === $step ) : ?>
308
 
309
- <h3><?php esc_html_e( 'You\'re ready to start using WP Job Manager!', 'wp-job-manager' ); ?></h3>
310
 
311
- <p><?php esc_html_e( 'Wondering what to do now? Here are some of the most common next steps:', 'wp-job-manager' ); ?></p>
312
 
313
  <ul class="wp-job-manager-next-steps">
314
- <li><a href="<?php echo esc_url( admin_url( 'edit.php?post_type=job_listing&page=job-manager-settings' ) ); ?>"><?php esc_html_e( 'Tweak your settings', 'wp-job-manager' ); ?></a></li>
315
- <li><a href="<?php echo esc_url( admin_url( 'post-new.php?post_type=job_listing' ) ); ?>"><?php esc_html_e( 'Add a job using the admin dashboard', 'wp-job-manager' ); ?></a></li>
316
- <?php
317
- $permalink = job_manager_get_permalink( 'jobs' );
318
- if ( $permalink ) {
319
- ?>
320
- <li><a href="<?php echo esc_url( $permalink ); ?>"><?php esc_html_e( 'View submitted job listings', 'wp-job-manager' ); ?></a></li>
321
- <?php } else { ?>
322
- <li><a href="https://wpjobmanager.com/document/shortcode-reference/#section-1"><?php esc_html_e( 'Add job listings to a page using the [jobs] shortcode', 'wp-job-manager' ); ?></a></li>
323
- <?php } ?>
324
-
325
- <?php
326
- $permalink = job_manager_get_permalink( 'submit_job_form' );
327
- if ( $permalink ) {
328
- ?>
329
- <li><a href="<?php echo esc_url( $permalink ); ?>"><?php esc_html_e( 'Add a job via the front-end', 'wp-job-manager' ); ?></a></li>
330
- <?php } else { ?>
331
- <li><a href="https://wpjobmanager.com/document/the-job-submission-form/"><?php esc_html_e( 'Learn to use the front-end job submission board', 'wp-job-manager' ); ?></a></li>
332
- <?php } ?>
333
-
334
- <?php
335
- $permalink = job_manager_get_permalink( 'job_dashboard' );
336
- if ( $permalink ) {
337
- ?>
338
- <li><a href="<?php echo esc_url( $permalink ); ?>"><?php esc_html_e( 'View the job dashboard', 'wp-job-manager' ); ?></a></li>
339
- <?php } else { ?>
340
- <li><a href="https://wpjobmanager.com/document/the-job-dashboard/"><?php esc_html_e( 'Learn to use the front-end job dashboard', 'wp-job-manager' ); ?></a></li>
341
- <?php } ?>
342
  </ul>
343
 
344
- <p>
345
- <?php
346
- echo wp_kses_post(
347
- sprintf(
348
- // translators: %1$s is the URL to WPJM support documentation; %2$s is the URL to WPJM support forums.
349
- __( 'If you need help, you can find more detail in our
350
- <a href="%1$s">support documentation</a> or post your question on the
351
- <a href="%2$s">WP Job Manager support forums</a>. Happy hiring!', 'wp-job-manager' ),
352
- 'https://wpjobmanager.com/documentation/',
353
- 'https://wordpress.org/support/plugin/wp-job-manager'
354
- )
355
- );
356
- ?>
357
- </p>
358
 
359
  <div class="wp-job-manager-support-the-plugin">
360
- <h3><?php esc_html_e( 'Support WP Job Manager\'s Ongoing Development', 'wp-job-manager' ); ?></h3>
361
- <p><?php esc_html_e( 'There are lots of ways you can support open source software projects like this one: contributing code, fixing a bug, assisting with non-English translation, or just telling your friends about WP Job Manager to help spread the word. We appreciate your support!', 'wp-job-manager' ); ?></p>
362
  <ul>
363
- <li class="icon-review"><a href="https://wordpress.org/support/view/plugin-reviews/wp-job-manager#postform"><?php esc_html_e( 'Leave a positive review', 'wp-job-manager' ); ?></a></li>
364
- <li class="icon-localization"><a href="https://translate.wordpress.org/projects/wp-plugins/wp-job-manager"><?php esc_html_e( 'Contribute a localization', 'wp-job-manager' ); ?></a></li>
365
- <li class="icon-code"><a href="https://github.com/mikejolley/WP-Job-Manager"><?php esc_html_e( 'Contribute code or report a bug', 'wp-job-manager' ); ?></a></li>
366
- <li class="icon-forum"><a href="https://wordpress.org/support/plugin/wp-job-manager"><?php esc_html_e( 'Help other users on the forums', 'wp-job-manager' ); ?></a></li>
367
  </ul>
368
  </div>
369
 
@@ -373,4 +251,4 @@ class WP_Job_Manager_Setup {
373
  }
374
  }
375
 
376
- WP_Job_Manager_Setup::instance();
4
  }
5
 
6
  /**
7
+ * WP_Job_Manager_Setup class.
 
 
 
8
  */
9
  class WP_Job_Manager_Setup {
10
 
11
  /**
12
+ * __construct function.
13
  *
14
+ * @access public
15
+ * @return void
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  */
17
  public function __construct() {
18
  add_action( 'admin_menu', array( $this, 'admin_menu' ), 12 );
19
  add_action( 'admin_head', array( $this, 'admin_head' ) );
20
  add_action( 'admin_init', array( $this, 'redirect' ) );
21
+ add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ), 12 );
 
 
22
  }
23
 
24
  /**
25
+ * admin_menu function.
26
+ *
27
+ * @access public
28
+ * @return void
29
  */
30
  public function admin_menu() {
31
+ add_dashboard_page( __( 'Setup', 'wp-job-manager' ), __( 'Setup', 'wp-job-manager' ), 'manage_options', 'job-manager-setup', array( $this, 'output' ) );
32
  }
33
 
34
  /**
35
+ * Add styles just for this page, and remove dashboard page links.
36
+ *
37
+ * @access public
38
+ * @return void
39
  */
40
  public function admin_head() {
41
  remove_submenu_page( 'index.php', 'job-manager-setup' );
42
  }
43
 
44
  /**
45
+ * Sends user to the setup page on first activation
46
  */
47
  public function redirect() {
48
+ // Bail if no activation redirect transient is set
49
+ if ( ! get_transient( '_job_manager_activation_redirect' ) ) {
50
  return;
51
+ }
52
 
53
+ if ( ! current_user_can( 'manage_options' ) ) {
54
+ return;
55
+ }
56
 
57
+ // Delete the redirect transient
58
  delete_transient( '_job_manager_activation_redirect' );
59
 
60
+ // Bail if activating from network, or bulk, or within an iFrame
61
  if ( is_network_admin() || isset( $_GET['activate-multi'] ) || defined( 'IFRAME_REQUEST' ) ) {
62
  return;
63
  }
64
 
65
+ if ( ( isset( $_GET['action'] ) && 'upgrade-plugin' == $_GET['action'] ) && ( isset( $_GET['plugin'] ) && strstr( $_GET['plugin'], 'wp-job-manager.php' ) ) ) {
66
  return;
67
  }
68
 
71
  }
72
 
73
  /**
74
+ * Enqueue scripts for setup page
75
  */
76
  public function admin_enqueue_scripts() {
77
+ wp_enqueue_style( 'job_manager_setup_css', JOB_MANAGER_PLUGIN_URL . '/assets/css/setup.css', array( 'dashicons' ) );
78
  }
79
 
80
  /**
81
+ * Create a page.
 
82
  * @param string $title
83
  * @param string $content
84
  * @param string $option
92
  'post_title' => $title,
93
  'post_content' => $content,
94
  'post_parent' => 0,
95
+ 'comment_status' => 'closed'
96
  );
97
+ $page_id = wp_insert_post( $page_data );
98
 
99
  if ( $option ) {
100
  update_option( $option, $page_id );
102
  }
103
 
104
  /**
105
+ * Output addons page
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  */
107
  public function output() {
108
  $step = ! empty( $_GET['step'] ) ? absint( $_GET['step'] ) : 1;
109
 
110
  if ( 3 === $step && ! empty( $_POST ) ) {
 
 
 
111
  $create_pages = isset( $_POST['wp-job-manager-create-page'] ) ? $_POST['wp-job-manager-create-page'] : array();
112
  $page_titles = $_POST['wp-job-manager-page-title'];
113
  $pages_to_create = array(
114
  'submit_job_form' => '[submit_job_form]',
115
  'job_dashboard' => '[job_dashboard]',
116
+ 'jobs' => '[jobs]'
117
  );
118
 
119
  foreach ( $pages_to_create as $page => $content ) {
125
  }
126
  ?>
127
  <div class="wrap wp_job_manager wp_job_manager_addons_wrap">
128
+ <h2><?php _e( 'WP Job Manager Setup', 'wp-job-manager' ); ?></h2>
129
 
130
  <ul class="wp-job-manager-setup-steps">
131
+ <li class="<?php if ( $step === 1 ) echo 'wp-job-manager-setup-active-step'; ?>"><?php _e( '1. Introduction', 'wp-job-manager' ); ?></li>
132
+ <li class="<?php if ( $step === 2 ) echo 'wp-job-manager-setup-active-step'; ?>"><?php _e( '2. Page Setup', 'wp-job-manager' ); ?></li>
133
+ <li class="<?php if ( $step === 3 ) echo 'wp-job-manager-setup-active-step'; ?>"><?php _e( '3. Done', 'wp-job-manager' ); ?></li>
 
 
 
 
134
  </ul>
135
 
136
  <?php if ( 1 === $step ) : ?>
137
 
138
+ <h3><?php _e( 'Setup Wizard Introduction', 'wp-job-manager' ); ?></h3>
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
+ <p><?php _e( 'Thanks for installing <em>WP Job Manager</em>!', 'wp-job-manager' ); ?></p>
141
+ <p><?php _e( 'This setup wizard will help you get started by creating the pages for job submission, job management, and listing your jobs.', 'wp-job-manager' ); ?></p>
142
+ <p><?php printf( __( 'If you want to skip the wizard and setup the pages and shortcodes yourself manually, the process is still relatively simple. Refer to the %sdocumentation%s for help.', 'wp-job-manager' ), '<a href="https://wpjobmanager.com/documentation/">', '</a>' ); ?></p>
143
 
144
+ <p class="submit">
145
+ <a href="<?php echo esc_url( add_query_arg( 'step', 2 ) ); ?>" class="button button-primary"><?php _e( 'Continue to page setup', 'wp-job-manager' ); ?></a>
146
+ <a href="<?php echo esc_url( add_query_arg( 'skip-job-manager-setup', 1, admin_url( 'index.php?page=job-manager-setup&step=3' ) ) ); ?>" class="button"><?php _e( 'Skip setup. I will setup the plugin manually', 'wp-job-manager' ); ?></a>
147
+ </p>
 
148
 
149
  <?php endif; ?>
150
  <?php if ( 2 === $step ) : ?>
151
 
152
+ <h3><?php _e( 'Page Setup', 'wp-job-manager' ); ?></h3>
153
+
154
+ <p><?php printf( __( '<em>WP Job Manager</em> includes %1$sshortcodes%2$s which can be used within your %3$spages%2$s to output content. These can be created for you below. For more information on the job shortcodes view the %4$sshortcode documentation%2$s.', 'wp-job-manager' ), '<a href="http://codex.wordpress.org/Shortcode" title="What is a shortcode?" target="_blank" class="help-page-link">', '</a>', '<a href="http://codex.wordpress.org/Pages" target="_blank" class="help-page-link">', '<a href="https://wpjobmanager.com/document/shortcode-reference/" target="_blank" class="help-page-link">' ); ?></p>
 
 
 
 
 
 
 
 
 
 
 
 
155
 
156
  <form action="<?php echo esc_url( add_query_arg( 'step', 3 ) ); ?>" method="post">
 
157
  <table class="wp-job-manager-shortcodes widefat">
158
  <thead>
159
  <tr>
160
  <th>&nbsp;</th>
161
+ <th><?php _e( 'Page Title', 'wp-job-manager' ); ?></th>
162
+ <th><?php _e( 'Page Description', 'wp-job-manager' ); ?></th>
163
+ <th><?php _e( 'Content Shortcode', 'wp-job-manager' ); ?></th>
164
  </tr>
165
  </thead>
166
  <tbody>
168
  <td><input type="checkbox" checked="checked" name="wp-job-manager-create-page[submit_job_form]" /></td>
169
  <td><input type="text" value="<?php echo esc_attr( _x( 'Post a Job', 'Default page title (wizard)', 'wp-job-manager' ) ); ?>" name="wp-job-manager-page-title[submit_job_form]" /></td>
170
  <td>
171
+ <p><?php _e( 'This page allows employers to post jobs to your website from the front-end.', 'wp-job-manager' ); ?></p>
172
+
173
+ <p><?php _e( 'If you do not want to accept submissions from users in this way (for example you just want to post jobs from the admin dashboard) you can skip creating this page.', 'wp-job-manager' ); ?></p>
174
  </td>
175
  <td><code>[submit_job_form]</code></td>
176
  </tr>
178
  <td><input type="checkbox" checked="checked" name="wp-job-manager-create-page[job_dashboard]" /></td>
179
  <td><input type="text" value="<?php echo esc_attr( _x( 'Job Dashboard', 'Default page title (wizard)', 'wp-job-manager' ) ); ?>" name="wp-job-manager-page-title[job_dashboard]" /></td>
180
  <td>
181
+ <p><?php _e( 'This page allows employers to manage and edit their own jobs from the front-end.', 'wp-job-manager' ); ?></p>
182
+
183
+ <p><?php _e( 'If you plan on managing all listings from the admin dashboard you can skip creating this page.', 'wp-job-manager' ); ?></p>
184
  </td>
185
  <td><code>[job_dashboard]</code></td>
186
  </tr>
187
  <tr>
188
  <td><input type="checkbox" checked="checked" name="wp-job-manager-create-page[jobs]" /></td>
189
  <td><input type="text" value="<?php echo esc_attr( _x( 'Jobs', 'Default page title (wizard)', 'wp-job-manager' ) ); ?>" name="wp-job-manager-page-title[jobs]" /></td>
190
+ <td><?php _e( 'This page allows users to browse, search, and filter job listings on the front-end of your site.', 'wp-job-manager' ); ?></td>
191
  <td><code>[jobs]</code></td>
192
  </tr>
193
  </tbody>
195
  <tr>
196
  <th colspan="4">
197
  <input type="submit" class="button button-primary" value="Create selected pages" />
198
+ <a href="<?php echo esc_url( add_query_arg( 'step', 3 ) ); ?>" class="button"><?php _e( 'Skip this step', 'wp-job-manager' ); ?></a>
199
  </th>
200
  </tr>
201
  </tfoot>
205
  <?php endif; ?>
206
  <?php if ( 3 === $step ) : ?>
207
 
208
+ <h3><?php _e( 'All Done!', 'wp-job-manager' ); ?></h3>
209
 
210
+ <p><?php _e( 'Looks like you\'re all set to start using the plugin. In case you\'re wondering where to go next:', 'wp-job-manager' ); ?></p>
211
 
212
  <ul class="wp-job-manager-next-steps">
213
+ <li><a href="<?php echo admin_url( 'edit.php?post_type=job_listing&page=job-manager-settings' ); ?>"><?php _e( 'Tweak the plugin settings', 'wp-job-manager' ); ?></a></li>
214
+ <li><a href="<?php echo admin_url( 'post-new.php?post_type=job_listing' ); ?>"><?php _e( 'Add a job via the back-end', 'wp-job-manager' ); ?></a></li>
215
+
216
+ <?php if ( $permalink = job_manager_get_permalink( 'submit_job_form' ) ) : ?>
217
+ <li><a href="<?php echo esc_url( $permalink ); ?>"><?php _e( 'Add a job via the front-end', 'wp-job-manager' ); ?></a></li>
218
+ <?php else : ?>
219
+ <li><a href="https://wpjobmanager.com/document/the-job-submission-form/"><?php _e( 'Find out more about the front-end job submission form', 'wp-job-manager' ); ?></a></li>
220
+ <?php endif; ?>
221
+
222
+ <?php if ( $permalink = job_manager_get_permalink( 'jobs' ) ) : ?>
223
+ <li><a href="<?php echo esc_url( $permalink ); ?>"><?php _e( 'View submitted job listings', 'wp-job-manager' ); ?></a></li>
224
+ <?php else : ?>
225
+ <li><a href="https://wpjobmanager.com/document/shortcode-reference/#section-1"><?php _e( 'Add the [jobs] shortcode to a page to list jobs', 'wp-job-manager' ); ?></a></li>
226
+ <?php endif; ?>
227
+
228
+ <?php if ( $permalink = job_manager_get_permalink( 'job_dashboard' ) ) : ?>
229
+ <li><a href="<?php echo esc_url( $permalink ); ?>"><?php _e( 'View the job dashboard', 'wp-job-manager' ); ?></a></li>
230
+ <?php else : ?>
231
+ <li><a href="https://wpjobmanager.com/document/the-job-dashboard/"><?php _e( 'Find out more about the front-end job dashboard', 'wp-job-manager' ); ?></a></li>
232
+ <?php endif; ?>
 
 
 
 
 
 
 
 
233
  </ul>
234
 
235
+ <p><?php printf( __( 'And don\'t forget, if you need any more help using <em>WP Job Manager</em> you can consult the %1$sdocumentation%2$s or %3$spost on the forums%2$s!', 'wp-job-manager' ), '<a href="https://wpjobmanager.com/documentation/">', '</a>', '<a href="https://wordpress.org/support/plugin/wp-job-manager">' ); ?></p>
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
  <div class="wp-job-manager-support-the-plugin">
238
+ <h3><?php _e( 'Support the Ongoing Development of this Plugin', 'wp-job-manager' ); ?></h3>
239
+ <p><?php _e( 'There are many ways to support open-source projects such as WP Job Manager, for example code contribution, translation, or even telling your friends how awesome the plugin (hopefully) is. Thanks in advance for your support - it is much appreciated!', 'wp-job-manager' ); ?></p>
240
  <ul>
241
+ <li class="icon-review"><a href="https://wordpress.org/support/view/plugin-reviews/wp-job-manager#postform"><?php _e( 'Leave a positive review', 'wp-job-manager' ); ?></a></li>
242
+ <li class="icon-localization"><a href="https://www.transifex.com/projects/p/wp-job-manager/"><?php _e( 'Contribute a localization', 'wp-job-manager' ); ?></a></li>
243
+ <li class="icon-code"><a href="https://github.com/mikejolley/WP-Job-Manager"><?php _e( 'Contribute code or report a bug', 'wp-job-manager' ); ?></a></li>
244
+ <li class="icon-forum"><a href="https://wordpress.org/support/plugin/wp-job-manager"><?php _e( 'Help other users on the forums', 'wp-job-manager' ); ?></a></li>
245
  </ul>
246
  </div>
247
 
251
  }
252
  }
253
 
254
+ new WP_Job_Manager_Setup();
includes/admin/class-wp-job-manager-taxonomy-meta.php DELETED
@@ -1,158 +0,0 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) {
3
- exit;
4
- }
5
-
6
- /**
7
- * Handles taxonomy meta custom fields. Just used for job type.
8
- *
9
- * @package wp-job-manager
10
- * @since 1.28.0
11
- */
12
- class WP_Job_Manager_Taxonomy_Meta {
13
- /**
14
- * The single instance of the class.
15
- *
16
- * @var self
17
- * @since 1.28.0
18
- */
19
- private static $_instance = null;
20
-
21
- /**
22
- * Allows for accessing single instance of class. Class should only be constructed once per call.
23
- *
24
- * @since 1.28.0
25
- * @static
26
- * @return self Main instance.
27
- */
28
- public static function instance() {
29
- if ( is_null( self::$_instance ) ) {
30
- self::$_instance = new self();
31
- }
32
- return self::$_instance;
33
- }
34
-
35
- /**
36
- * WP_Job_Manager_Taxonomy_Meta constructor.
37
- */
38
- public function __construct() {
39
- if ( wpjm_job_listing_employment_type_enabled() ) {
40
- add_action( 'job_listing_type_edit_form_fields', array( $this, 'display_schema_org_employment_type_field' ), 10, 2 );
41
- add_action( 'job_listing_type_add_form_fields', array( $this, 'add_form_display_schema_org_employment_type_field' ), 10 );
42
- add_action( 'edited_job_listing_type', array( $this, 'set_schema_org_employment_type_field' ), 10, 2 );
43
- add_action( 'created_job_listing_type', array( $this, 'set_schema_org_employment_type_field' ), 10, 2 );
44
- add_filter( 'manage_edit-job_listing_type_columns', array( $this, 'add_employment_type_column' ) );
45
- add_filter( 'manage_job_listing_type_custom_column', array( $this, 'add_employment_type_column_content' ), 10, 3 );
46
- add_filter( 'manage_edit-job_listing_type_sortable_columns', array( $this, 'add_employment_type_column_sortable' ) );
47
- }
48
- }
49
-
50
- /**
51
- * Set the employment type field when creating/updating a job type item.
52
- *
53
- * @param int $term_id Term ID.
54
- * @param int $tt_id Taxonomy type ID.
55
- */
56
- public function set_schema_org_employment_type_field( $term_id, $tt_id ) {
57
- $employment_types = wpjm_job_listing_employment_type_options();
58
- if ( isset( $_POST['employment_type'] ) && isset( $employment_types[ $_POST['employment_type'] ] ) ) {
59
- update_term_meta( $term_id, 'employment_type', $_POST['employment_type'] );
60
- } elseif ( isset( $_POST['employment_type'] ) ) {
61
- delete_term_meta( $term_id, 'employment_type' );
62
- }
63
- }
64
-
65
- /**
66
- * Add the option to select schema.org employmentType for job type on the edit meta field form.
67
- *
68
- * @param WP_Term $term Term object.
69
- * @param string $taxonomy Taxonomy slug.
70
- */
71
- public function display_schema_org_employment_type_field( $term, $taxonomy ) {
72
- $employment_types = wpjm_job_listing_employment_type_options();
73
- $current_employment_type = get_term_meta( $term->term_id, 'employment_type', true );
74
-
75
- if ( ! empty( $employment_types ) ) {
76
- ?>
77
- <tr class="form-field term-group-wrap">
78
- <th scope="row"><label for="feature-group"><?php esc_html_e( 'Employment Type', 'wp-job-manager' ); ?></label></th>
79
- <td><select class="postform" id="employment_type" name="employment_type">
80
- <option value=""></option>
81
- <?php foreach ( $employment_types as $key => $employment_type ) : ?>
82
- <option value="<?php echo esc_attr( $key ); ?>" <?php selected( $current_employment_type, $key ); ?>><?php echo esc_html( $employment_type ); ?></option>
83
- <?php endforeach; ?>
84
- </select></td>
85
- </tr>
86
- <?php
87
- }
88
- }
89
-
90
- /**
91
- * Add the option to select schema.org employmentType for job type on the add meta field form.
92
- *
93
- * @param string $taxonomy Taxonomy slug.
94
- */
95
- public function add_form_display_schema_org_employment_type_field( $taxonomy ) {
96
- $employment_types = wpjm_job_listing_employment_type_options();
97
-
98
- if ( ! empty( $employment_types ) ) {
99
- ?>
100
- <div class="form-field term-group">
101
- <label for="feature-group"><?php esc_html_e( 'Employment Type', 'wp-job-manager' ); ?></label>
102
- <select class="postform" id="employment_type" name="employment_type">
103
- <option value=""></option>
104
- <?php foreach ( $employment_types as $key => $employment_type ) : ?>
105
- <option value="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $employment_type ); ?></option>
106
- <?php endforeach; ?>
107
- </select>
108
- </div>
109
- <?php
110
- }
111
- }
112
-
113
- /**
114
- * Adds the Employment Type column when listing job type terms in WP Admin.
115
- *
116
- * @param array $columns
117
- * @return array
118
- */
119
- public function add_employment_type_column( $columns ) {
120
- $columns['employment_type'] = __( 'Employment Type', 'wp-job-manager' );
121
- return $columns;
122
- }
123
-
124
- /**
125
- * Adds the Employment Type column as a sortable column when listing job type terms in WP Admin.
126
- *
127
- * @param array $sortable
128
- * @return array
129
- */
130
- public function add_employment_type_column_sortable( $sortable ) {
131
- $sortable['employment_type'] = 'employment_type';
132
- return $sortable;
133
- }
134
-
135
- /**
136
- * Adds the Employment Type column content when listing job type terms in WP Admin.
137
- *
138
- * @param string $content
139
- * @param string $column_name
140
- * @param int $term_id
141
- * @return string
142
- */
143
- public function add_employment_type_column_content( $content, $column_name, $term_id ) {
144
- if ( 'employment_type' !== $column_name ) {
145
- return $content;
146
- }
147
- $employment_types = wpjm_job_listing_employment_type_options();
148
- $term_id = absint( $term_id );
149
- $term_employment_type = get_term_meta( $term_id, 'employment_type', true );
150
-
151
- if ( ! empty( $term_employment_type ) && isset( $employment_types[ $term_employment_type ] ) ) {
152
- $content .= esc_attr( $employment_types[ $term_employment_type ] );
153
- }
154
- return $content;
155
- }
156
- }
157
-
158
- WP_Job_Manager_Taxonomy_Meta::instance();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/admin/class-wp-job-manager-writepanels.php CHANGED
@@ -1,40 +1,13 @@
1
  <?php
2
- if ( ! defined( 'ABSPATH' ) ) {
3
- exit; // Exit if accessed directly.
4
- }
5
 
6
- /**
7
- * Handles the management of Job Listing meta fields.
8
- *
9
- * @package wp-job-manager
10
- * @since 1.0.0
11
- */
12
  class WP_Job_Manager_Writepanels {
13
 
14
  /**
15
- * The single instance of the class.
16
  *
17
- * @var self
18
- * @since 1.26.0
19
- */
20
- private static $_instance = null;
21
-
22
- /**
23
- * Allows for accessing single instance of class. Class should only be constructed once per call.
24
- *
25
- * @since 1.26.0
26
- * @static
27
- * @return self Main instance.
28
- */
29
- public static function instance() {
30
- if ( is_null( self::$_instance ) ) {
31
- self::$_instance = new self();
32
- }
33
- return self::$_instance;
34
- }
35
-
36
- /**
37
- * Constructor.
38
  */
39
  public function __construct() {
40
  add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
@@ -43,9 +16,10 @@ class WP_Job_Manager_Writepanels {
43
  }
44
 
45
  /**
46
- * Returns configuration for custom fields on Job Listing posts.
47
  *
48
- * @return array
 
49
  */
50
  public function job_listing_fields() {
51
  global $post;
@@ -53,86 +27,75 @@ class WP_Job_Manager_Writepanels {
53
  $current_user = wp_get_current_user();
54
 
55
  $fields = array(
56
- '_job_location' => array(
57
- 'label' => __( 'Location', 'wp-job-manager' ),
58
  'placeholder' => __( 'e.g. "London"', 'wp-job-manager' ),
59
  'description' => __( 'Leave this blank if the location is not important.', 'wp-job-manager' ),
60
- 'priority' => 1,
61
  ),
62
- '_application' => array(
63
  'label' => __( 'Application Email or URL', 'wp-job-manager' ),
64
  'placeholder' => __( 'URL or email which applicants use to apply', 'wp-job-manager' ),
65
  'description' => __( 'This field is required for the "application" area to appear beneath the listing.', 'wp-job-manager' ),
66
  'value' => metadata_exists( 'post', $post->ID, '_application' ) ? get_post_meta( $post->ID, '_application', true ) : $current_user->user_email,
67
- 'priority' => 2,
68
  ),
69
- '_company_name' => array(
70
  'label' => __( 'Company Name', 'wp-job-manager' ),
71
  'placeholder' => '',
72
- 'priority' => 3,
73
  ),
74
  '_company_website' => array(
75
  'label' => __( 'Company Website', 'wp-job-manager' ),
76
  'placeholder' => '',
77
- 'priority' => 4,
78
  ),
79
  '_company_tagline' => array(
80
  'label' => __( 'Company Tagline', 'wp-job-manager' ),
81
  'placeholder' => __( 'Brief description about the company', 'wp-job-manager' ),
82
- 'priority' => 5,
83
  ),
84
  '_company_twitter' => array(
85
  'label' => __( 'Company Twitter', 'wp-job-manager' ),
86
  'placeholder' => '@yourcompany',
87
- 'priority' => 6,
88
  ),
89
- '_company_video' => array(
90
  'label' => __( 'Company Video', 'wp-job-manager' ),
91
  'placeholder' => __( 'URL to the company video', 'wp-job-manager' ),
92
  'type' => 'file',
93
- 'priority' => 8,
94
  ),
95
- '_filled' => array(
96
  'label' => __( 'Position Filled', 'wp-job-manager' ),
97
  'type' => 'checkbox',
98
  'priority' => 9,
99
  'description' => __( 'Filled listings will no longer accept applications.', 'wp-job-manager' ),
100
- ),
101
  );
102
  if ( $current_user->has_cap( 'manage_job_listings' ) ) {
103
- $fields['_featured'] = array(
104
  'label' => __( 'Featured Listing', 'wp-job-manager' ),
105
  'type' => 'checkbox',
106
  'description' => __( 'Featured listings will be sticky during searches, and can be styled differently.', 'wp-job-manager' ),
107
- 'priority' => 10,
108
  );
109
- $job_expires = get_post_meta( $post->ID, '_job_expires', true );
110
  $fields['_job_expires'] = array(
111
  'label' => __( 'Listing Expiry Date', 'wp-job-manager' ),
112
  'priority' => 11,
113
- 'classes' => array( 'job-manager-datepicker' ),
114
- 'placeholder' => ! empty( $job_expires ) ? null : date_i18n( get_option( 'date_format' ), strtotime( calculate_job_expiry( $post->ID ) ) ),
115
- 'value' => ! empty( $job_expires ) ? date( 'Y-m-d', strtotime( $job_expires ) ) : '',
116
  );
117
  }
118
  if ( $current_user->has_cap( 'edit_others_job_listings' ) ) {
119
  $fields['_job_author'] = array(
120
  'label' => __( 'Posted by', 'wp-job-manager' ),
121
  'type' => 'author',
122
- 'priority' => 12,
123
  );
124
  }
125
 
126
- /**
127
- * Filters job listing data fields for WP Admin post editor.
128
- *
129
- * @since 1.0.0
130
- * @since 1.27.0 $post_id was added
131
- *
132
- * @param array $fields
133
- * @param int $post_id
134
- */
135
- $fields = apply_filters( 'job_manager_job_listing_data_fields', $fields, $post->ID );
136
 
137
  uasort( $fields, array( $this, 'sort_by_priority' ) );
138
 
@@ -140,119 +103,32 @@ class WP_Job_Manager_Writepanels {
140
  }
141
 
142
  /**
143
- * Sorts array of custom fields by priority value.
144
- *
145
- * @param array $a
146
- * @param array $b
147
- * @return int
148
  */
149
  protected function sort_by_priority( $a, $b ) {
150
- if ( ! isset( $a['priority'] ) || ! isset( $b['priority'] ) || $a['priority'] === $b['priority'] ) {
151
- return 0;
152
- }
153
- return ( $a['priority'] < $b['priority'] ) ? -1 : 1;
154
  }
155
 
156
  /**
157
- * Handles the hooks to add custom field meta boxes.
 
 
 
158
  */
159
  public function add_meta_boxes() {
160
  global $wp_post_types;
161
 
162
- // translators: Placeholder %s is the singular name for a job listing post type.
163
  add_meta_box( 'job_listing_data', sprintf( __( '%s Data', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name ), array( $this, 'job_listing_data' ), 'job_listing', 'normal', 'high' );
164
- if ( ! get_option( 'job_manager_enable_types' ) || 0 === intval( wp_count_terms( 'job_listing_type' ) ) ) {
165
- remove_meta_box( 'job_listing_typediv', 'job_listing', 'side' );
166
- } elseif ( false === job_manager_multi_job_type() ) {
167
- remove_meta_box( 'job_listing_typediv', 'job_listing', 'side' );
168
- $job_listing_type = get_taxonomy( 'job_listing_type' );
169
- add_meta_box( 'job_listing_type', $job_listing_type->labels->menu_name, array( $this, 'job_listing_metabox' ), 'job_listing', 'side', 'core' );
170
- }
171
- }
172
-
173
- /**
174
- * Displays job listing metabox.
175
- *
176
- * @param int|WP_Post $post
177
- */
178
- public function job_listing_metabox( $post ) {
179
- // Set up the taxonomy object and get terms.
180
- $taxonomy = 'job_listing_type';
181
- $tax = get_taxonomy( $taxonomy );// This is the taxonomy object.
182
-
183
- // The name of the form.
184
- $name = 'tax_input[' . $taxonomy . ']';
185
-
186
- // Get all the terms for this taxonomy.
187
- $terms = get_terms(
188
- array(
189
- 'taxonomy' => $taxonomy,
190
- 'hide_empty' => 0,
191
- )
192
- );
193
- $postterms = get_the_terms( $post->ID, $taxonomy );
194
- $current = ( $postterms ? array_pop( $postterms ) : false );
195
- $current = ( $current ? $current->term_id : 0 );
196
- // Get current and popular terms.
197
- $popular = get_terms(
198
- array(
199
- 'taxonomy' => $taxonomy,
200
- 'orderby' => 'count',
201
- 'order' => 'DESC',
202
- 'number' => 10,
203
- 'hierarchical' => false,
204
- )
205
- );
206
- $postterms = get_the_terms( $post->ID, $taxonomy );
207
- $current = ( $postterms ? array_pop( $postterms ) : false );
208
- $current = ( $current ? $current->term_id : 0 );
209
- ?>
210
-
211
- <div id="taxonomy-<?php echo esc_attr( $taxonomy ); ?>" class="categorydiv">
212
-
213
- <!-- Display tabs-->
214
- <ul id="<?php echo esc_attr( $taxonomy ); ?>-tabs" class="category-tabs">
215
- <li class="tabs"><a href="#<?php echo esc_attr( $taxonomy ); ?>-all" tabindex="3"><?php echo esc_html( $tax->labels->all_items ); ?></a></li>
216
- <li class="hide-if-no-js"><a href="#<?php echo esc_attr( $taxonomy ); ?>-pop" tabindex="3"><?php esc_html_e( 'Most Used', 'wp-job-manager' ); ?></a></li>
217
- </ul>
218
-
219
- <!-- Display taxonomy terms -->
220
- <div id="<?php echo esc_attr( $taxonomy ); ?>-all" class="tabs-panel">
221
- <ul id="<?php echo esc_attr( $taxonomy ); ?>checklist" class="list:<?php echo esc_attr( $taxonomy ); ?> categorychecklist form-no-clear">
222
- <?php
223
- foreach ( $terms as $term ) {
224
- $id = $taxonomy . '-' . $term->term_id;
225
- echo '<li id="' . esc_attr( $id ) . '"><label class="selectit">';
226
- echo '<input type="radio" id="in-' . esc_attr( $id ) . '" name="' . esc_attr( $name ) . '" ' . checked( $current, $term->term_id, false ) . ' value="' . esc_attr( $term->term_id ) . '" />' . esc_attr( $term->name ) . '<br />';
227
- echo '</label></li>';
228
- }
229
- ?>
230
- </ul>
231
- </div>
232
-
233
- <!-- Display popular taxonomy terms -->
234
- <div id="<?php echo esc_attr( $taxonomy ); ?>-pop" class="tabs-panel" style="display: none;">
235
- <ul id="<?php echo esc_attr( $taxonomy ); ?>checklist-pop" class="categorychecklist form-no-clear" >
236
- <?php
237
- foreach ( $popular as $term ) {
238
- $id = 'popular-' . $taxonomy . '-' . $term->term_id;
239
- echo '<li id="' . esc_attr( $id ) . '"><label class="selectit">';
240
- echo '<input type="radio" id="in-' . esc_attr( $id ) . '" ' . checked( $current, $term->term_id, false ) . ' value="' . esc_attr( $term->term_id ) . '" />' . esc_attr( $term->name ) . '<br />';
241
- echo '</label></li>';
242
- }
243
- ?>
244
- </ul>
245
- </div>
246
-
247
- </div>
248
- <?php
249
  }
250
 
251
  /**
252
- * Displays label and file input field.
253
  *
254
- * @param string $key
255
- * @param array $field
256
  */
257
  public static function input_file( $key, $field ) {
258
  global $thepostid;
@@ -270,27 +146,17 @@ class WP_Job_Manager_Writepanels {
270
  }
271
  ?>
272
  <p class="form-field">
273
- <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( wp_strip_all_tags( $field['label'] ) ); ?>:
274
- <?php if ( ! empty( $field['description'] ) ) : ?>
275
- <span class="tips" data-tip="<?php echo esc_attr( $field['description'] ); ?>">[?]</span>
276
- <?php endif; ?>
277
- </label>
278
  <?php
279
  if ( ! empty( $field['multiple'] ) ) {
280
  foreach ( (array) $field['value'] as $value ) {
281
- ?>
282
- <span class="file_url"><input type="text" name="<?php echo esc_attr( $name ); ?>[]" placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" value="<?php echo esc_attr( $value ); ?>" /><button class="button button-small wp_job_manager_upload_file_button" data-uploader_button_text="<?php esc_attr_e( 'Use file', 'wp-job-manager' ); ?>"><?php esc_html_e( 'Upload', 'wp-job-manager' ); ?></button><button class="button button-small wp_job_manager_view_file_button"><?php esc_html_e( 'View', 'wp-job-manager' ); ?></button></span>
283
- <?php
284
  }
285
  } else {
286
- ?>
287
- <span class="file_url"><input type="text" name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $key ); ?>" placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" value="<?php echo esc_attr( $field['value'] ); ?>" /><button class="button button-small wp_job_manager_upload_file_button" data-uploader_button_text="<?php esc_attr_e( 'Use file', 'wp-job-manager' ); ?>"><?php esc_html_e( 'Upload', 'wp-job-manager' ); ?></button><button class="button button-small wp_job_manager_view_file_button"><?php esc_html_e( 'View', 'wp-job-manager' ); ?></button></span>
288
- <?php
289
  }
290
  if ( ! empty( $field['multiple'] ) ) {
291
- ?>
292
- <button class="button button-small wp_job_manager_add_another_file_button" data-field_name="<?php echo esc_attr( $key ); ?>" data-field_placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" data-uploader_button_text="<?php esc_attr_e( 'Use file', 'wp-job-manager' ); ?>" data-uploader_button="<?php esc_attr_e( 'Upload', 'wp-job-manager' ); ?>" data-view_button="<?php esc_attr_e( 'View', 'wp-job-manager' ); ?>"><?php esc_html_e( 'Add file', 'wp-job-manager' ); ?></button>
293
- <?php
294
  }
295
  ?>
296
  </p>
@@ -298,10 +164,10 @@ class WP_Job_Manager_Writepanels {
298
  }
299
 
300
  /**
301
- * Displays label and text input field.
302
  *
303
- * @param string $key
304
- * @param array $field
305
  */
306
  public static function input_text( $key, $field ) {
307
  global $thepostid;
@@ -314,85 +180,19 @@ class WP_Job_Manager_Writepanels {
314
  } else {
315
  $name = $key;
316
  }
317
- if ( ! empty( $field['classes'] ) ) {
318
- $classes = implode( ' ', is_array( $field['classes'] ) ? $field['classes'] : array( $field['classes'] ) );
319
- } else {
320
- $classes = '';
321
- }
322
- ?>
323
- <p class="form-field">
324
- <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( wp_strip_all_tags( $field['label'] ) ); ?>:
325
- <?php if ( ! empty( $field['description'] ) ) : ?>
326
- <span class="tips" data-tip="<?php echo esc_attr( $field['description'] ); ?>">[?]</span>
327
- <?php endif; ?>
328
- </label>
329
- <input type="text" autocomplete="off" name="<?php echo esc_attr( $name ); ?>" class="<?php echo esc_attr( $classes ); ?>" id="<?php echo esc_attr( $key ); ?>" placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" value="<?php echo esc_attr( $field['value'] ); ?>" />
330
- </p>
331
- <?php
332
- }
333
-
334
- /**
335
- * Just displays information.
336
- *
337
- * @since 1.27.0
338
- *
339
- * @param string $key
340
- * @param array $field
341
- */
342
- public static function input_info( $key, $field ) {
343
- self::input_hidden( $key, $field );
344
- }
345
-
346
- /**
347
- * Displays information and/or hidden input.
348
- *
349
- * @since 1.27.0
350
- *
351
- * @param string $key
352
- * @param array $field
353
- */
354
- public static function input_hidden( $key, $field ) {
355
- global $thepostid;
356
-
357
- if ( 'hidden' === $field['type'] && ! isset( $field['value'] ) ) {
358
- $field['value'] = get_post_meta( $thepostid, $key, true );
359
- }
360
- if ( ! empty( $field['name'] ) ) {
361
- $name = $field['name'];
362
- } else {
363
- $name = $key;
364
- }
365
- if ( ! empty( $field['classes'] ) ) {
366
- $classes = implode( ' ', is_array( $field['classes'] ) ? $field['classes'] : array( $field['classes'] ) );
367
- } else {
368
- $classes = '';
369
- }
370
- if ( 'hidden' === $field['type'] ) {
371
- if ( empty( $field['label'] ) ) {
372
- echo '<input type="hidden" name="' . esc_attr( $name ) . '" class="' . esc_attr( $classes ) . '" id="' . esc_attr( $key ) . '" value="' . esc_attr( $field['value'] ) . '" />';
373
- return;
374
- }
375
- }
376
  ?>
377
  <p class="form-field">
378
- <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( wp_strip_all_tags( $field['label'] ) ); ?>:
379
- <?php if ( ! empty( $field['description'] ) ) : ?>
380
- <span class="tips" data-tip="<?php echo esc_attr( $field['description'] ); ?>">[?]</span>
381
- <?php endif; ?>
382
- </label>
383
- <?php if ( ! empty( $field['information'] ) ) : ?>
384
- <span class="information"><?php echo wp_kses( $field['information'], array( 'a' => array( 'href' => array() ) ) ); ?></span>
385
- <?php endif; ?>
386
- <?php echo '<input type="hidden" name="' . esc_attr( $name ) . '" class="' . esc_attr( $classes ) . '" id="' . esc_attr( $key ) . '" value="' . esc_attr( $field['value'] ) . '" />'; ?>
387
  </p>
388
  <?php
389
  }
390
 
391
  /**
392
- * Displays label and textarea input field.
393
  *
394
- * @param string $key
395
- * @param array $field
396
  */
397
  public static function input_textarea( $key, $field ) {
398
  global $thepostid;
@@ -407,21 +207,17 @@ class WP_Job_Manager_Writepanels {
407
  }
408
  ?>
409
  <p class="form-field">
410
- <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( wp_strip_all_tags( $field['label'] ) ); ?>:
411
- <?php if ( ! empty( $field['description'] ) ) : ?>
412
- <span class="tips" data-tip="<?php echo esc_attr( $field['description'] ); ?>">[?]</span>
413
- <?php endif; ?>
414
- </label>
415
  <textarea name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $key ); ?>" placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>"><?php echo esc_html( $field['value'] ); ?></textarea>
416
  </p>
417
  <?php
418
  }
419
 
420
  /**
421
- * Displays label and select input field.
422
  *
423
- * @param string $key
424
- * @param array $field
425
  */
426
  public static function input_select( $key, $field ) {
427
  global $thepostid;
@@ -436,22 +232,10 @@ class WP_Job_Manager_Writepanels {
436
  }
437
  ?>
438
  <p class="form-field">
439
- <label for="<?php echo esc_attr( $key ); ?>">
440
- <?php echo esc_html( wp_strip_all_tags( $field['label'] ) ); ?>:
441
- <?php if ( ! empty( $field['description'] ) ) : ?>
442
- <span class="tips" data-tip="<?php echo esc_attr( $field['description'] ); ?>">[?]</span>
443
- <?php endif; ?>
444
- </label>
445
  <select name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $key ); ?>">
446
  <?php foreach ( $field['options'] as $key => $value ) : ?>
447
- <option
448
- value="<?php echo esc_attr( $key ); ?>"
449
- <?php
450
- if ( isset( $field['value'] ) ) {
451
- selected( $field['value'], $key );
452
- }
453
- ?>
454
- ><?php echo esc_html( $value ); ?></option>
455
  <?php endforeach; ?>
456
  </select>
457
  </p>
@@ -459,10 +243,10 @@ class WP_Job_Manager_Writepanels {
459
  }
460
 
461
  /**
462
- * Displays label and multi-select input field.
463
  *
464
- * @param string $key
465
- * @param array $field
466
  */
467
  public static function input_multiselect( $key, $field ) {
468
  global $thepostid;
@@ -477,21 +261,10 @@ class WP_Job_Manager_Writepanels {
477
  }
478
  ?>
479
  <p class="form-field">
480
- <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( wp_strip_all_tags( $field['label'] ) ); ?>:
481
- <?php if ( ! empty( $field['description'] ) ) : ?>
482
- <span class="tips" data-tip="<?php echo esc_attr( $field['description'] ); ?>">[?]</span>
483
- <?php endif; ?>
484
- </label>
485
  <select multiple="multiple" name="<?php echo esc_attr( $name ); ?>[]" id="<?php echo esc_attr( $key ); ?>">
486
  <?php foreach ( $field['options'] as $key => $value ) : ?>
487
- <option value="<?php echo esc_attr( $key ); ?>"
488
- <?php
489
- if ( ! empty( $field['value'] ) && is_array( $field['value'] ) ) {
490
- // phpcs:ignore WordPress.PHP.StrictInArray
491
- selected( in_array( $key, $field['value'] ), true );
492
- }
493
- ?>
494
- ><?php echo esc_html( $value ); ?></option>
495
  <?php endforeach; ?>
496
  </select>
497
  </p>
@@ -499,10 +272,10 @@ class WP_Job_Manager_Writepanels {
499
  }
500
 
501
  /**
502
- * Displays label and checkbox input field.
503
  *
504
- * @param string $key
505
- * @param array $field
506
  */
507
  public static function input_checkbox( $key, $field ) {
508
  global $thepostid;
@@ -517,20 +290,18 @@ class WP_Job_Manager_Writepanels {
517
  }
518
  ?>
519
  <p class="form-field form-field-checkbox">
520
- <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( wp_strip_all_tags( $field['label'] ) ); ?></label>
521
  <input type="checkbox" class="checkbox" name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $key ); ?>" value="1" <?php checked( $field['value'], 1 ); ?> />
522
- <?php if ( ! empty( $field['description'] ) ) : ?>
523
- <span class="description"><?php echo wp_kses_post( $field['description'] ); ?></span>
524
- <?php endif; ?>
525
  </p>
526
  <?php
527
  }
528
 
529
  /**
530
- * Displays label and author select field.
531
  *
532
- * @param string $key
533
- * @param array $field
534
  */
535
  public static function input_author( $key, $field ) {
536
  global $thepostid, $post;
@@ -547,30 +318,29 @@ class WP_Job_Manager_Writepanels {
547
  $name = ! empty( $field['name'] ) ? $field['name'] : $key;
548
  ?>
549
  <p class="form-field form-field-author">
550
- <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( wp_strip_all_tags( $field['label'] ) ); ?>:</label>
551
  <span class="current-author">
552
  <?php
553
- if ( $posted_by ) {
554
- echo '<a href="' . esc_url( admin_url( 'user-edit.php?user_id=' . absint( $author_id ) ) ) . '">#' . absint( $author_id ) . ' &ndash; ' . esc_html( $posted_by->user_login ) . '</a>';
555
- } else {
556
- esc_html_e( 'Guest User', 'wp-job-manager' );
557
- }
558
- ?>
559
- <a href="#" class="change-author button button-small"><?php esc_html_e( 'Change', 'wp-job-manager' ); ?></a>
560
  </span>
561
  <span class="hidden change-author">
562
  <input type="number" name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $key ); ?>" step="1" value="<?php echo esc_attr( $author_id ); ?>" style="width: 4em;" />
563
- <span class="description"><?php esc_html_e( 'Enter the ID of the user, or leave blank if submitted by a guest.', 'wp-job-manager' ); ?></span>
564
  </span>
565
  </p>
566
  <?php
567
  }
568
 
569
  /**
570
- * Displays label and radio input field.
571
  *
572
- * @param string $key
573
- * @param array $field
574
  */
575
  public static function input_radio( $key, $field ) {
576
  global $thepostid;
@@ -585,24 +355,24 @@ class WP_Job_Manager_Writepanels {
585
  }
586
  ?>
587
  <p class="form-field form-field-checkbox">
588
- <label><?php echo esc_html( wp_strip_all_tags( $field['label'] ) ); ?></label>
589
  <?php foreach ( $field['options'] as $option_key => $value ) : ?>
590
  <label><input type="radio" class="radio" name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>" value="<?php echo esc_attr( $option_key ); ?>" <?php checked( $field['value'], $option_key ); ?> /> <?php echo esc_html( $value ); ?></label>
591
  <?php endforeach; ?>
592
- <?php if ( ! empty( $field['description'] ) ) : ?>
593
- <span class="description"><?php echo wp_kses_post( $field['description'] ); ?></span>
594
- <?php endif; ?>
595
  </p>
596
  <?php
597
  }
598
 
599
  /**
600
- * Displays metadata fields for Job Listings.
601
  *
602
- * @param int|WP_Post $post
 
 
603
  */
604
  public function job_listing_data( $post ) {
605
- global $post, $thepostid, $wp_post_types;
606
 
607
  $thepostid = $post->ID;
608
 
@@ -622,108 +392,90 @@ class WP_Job_Manager_Writepanels {
622
  }
623
  }
624
 
625
- $user_edited_date = get_post_meta( $post->ID, '_job_edited', true );
626
- if ( $user_edited_date ) {
627
- echo '<p class="form-field">';
628
- // translators: %1$s is placeholder for singular name of the job listing post type; %2$s is the intl formatted date the listing was last modified.
629
- echo '<em>' . sprintf( esc_html__( '%1$s was last modified by the user on %2$s.', 'wp-job-manager' ), esc_html( $wp_post_types['job_listing']->labels->singular_name ), esc_html( date_i18n( get_option( 'date_format' ), $user_edited_date ) ) ) . '</em>';
630
- echo '</p>';
631
- }
632
-
633
  do_action( 'job_manager_job_listing_data_end', $thepostid );
634
 
635
  echo '</div>';
636
  }
637
 
638
  /**
639
- * Handles `save_post` action.
640
  *
641
- * @param int $post_id
642
- * @param WP_Post $post
 
 
643
  */
644
  public function save_post( $post_id, $post ) {
645
- if ( empty( $post_id ) || empty( $post ) || empty( $_POST ) ) {
646
- return;
647
- }
648
- if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
649
- return;
650
- }
651
- if ( is_int( wp_is_post_revision( $post ) ) ) {
652
- return;
653
- }
654
- if ( is_int( wp_is_post_autosave( $post ) ) ) {
655
- return;
656
- }
657
- if ( empty( $_POST['job_manager_nonce'] ) || ! wp_verify_nonce( $_POST['job_manager_nonce'], 'save_meta_data' ) ) {
658
- return;
659
- }
660
- if ( ! current_user_can( 'edit_post', $post_id ) ) {
661
- return;
662
- }
663
- if ( 'job_listing' !== $post->post_type ) {
664
- return;
665
- }
666
 
667
  do_action( 'job_manager_save_job_listing', $post_id, $post );
668
  }
669
 
670
  /**
671
- * Handles the actual saving of job listing data fields.
672
  *
673
- * @param int $post_id
674
- * @param WP_Post $post (Unused).
 
 
675
  */
676
  public function save_job_listing_data( $post_id, $post ) {
677
  global $wpdb;
678
 
679
- // These need to exist.
680
  add_post_meta( $post_id, '_filled', 0, true );
681
  add_post_meta( $post_id, '_featured', 0, true );
682
 
683
- // Save fields.
684
  foreach ( $this->job_listing_fields() as $key => $field ) {
685
- if ( isset( $field['type'] ) && 'info' === $field['type'] ) {
686
- continue;
687
- }
688
-
689
- // Expirey date.
690
  if ( '_job_expires' === $key ) {
691
- if ( empty( $_POST[ $key ] ) ) {
692
- if ( get_option( 'job_manager_submission_duration' ) ) {
693
- update_post_meta( $post_id, $key, calculate_job_expiry( $post_id ) );
694
- } else {
695
- delete_post_meta( $post_id, $key );
696
- }
697
- } else {
698
  update_post_meta( $post_id, $key, date( 'Y-m-d', strtotime( sanitize_text_field( $_POST[ $key ] ) ) ) );
 
 
699
  }
700
- } elseif ( '_job_location' === $key ) {
701
- // Locations.
702
- $updated_result = update_post_meta( $post_id, $key, sanitize_text_field( $_POST[ $key ] ) );
703
- if ( ! $updated_result && apply_filters( 'job_manager_geolocation_enabled', true ) && ! WP_Job_Manager_Geocode::has_location_data( $post_id ) ) {
704
- // First time generation for job location data.
 
 
705
  WP_Job_Manager_Geocode::generate_location_data( $post_id, sanitize_text_field( $_POST[ $key ] ) );
706
  }
707
- } elseif ( '_job_author' === $key ) {
 
 
708
  $wpdb->update( $wpdb->posts, array( 'post_author' => $_POST[ $key ] > 0 ? absint( $_POST[ $key ] ) : 0 ), array( 'ID' => $post_id ) );
709
- } elseif ( '_application' === $key ) {
710
- update_post_meta( $post_id, $key, sanitize_text_field( is_email( $_POST[ $key ] ) ? $_POST[ $key ] : urldecode( $_POST[ $key ] ) ) );
711
- } else {
712
- // Everything else.
 
 
 
 
713
  $type = ! empty( $field['type'] ) ? $field['type'] : '';
714
 
715
  switch ( $type ) {
716
- case 'textarea':
717
  update_post_meta( $post_id, $key, wp_kses_post( stripslashes( $_POST[ $key ] ) ) );
718
- break;
719
- case 'checkbox':
720
  if ( isset( $_POST[ $key ] ) ) {
721
  update_post_meta( $post_id, $key, 1 );
722
  } else {
723
  update_post_meta( $post_id, $key, 0 );
724
  }
725
- break;
726
- default:
727
  if ( ! isset( $_POST[ $key ] ) ) {
728
  continue;
729
  } elseif ( is_array( $_POST[ $key ] ) ) {
@@ -731,48 +483,11 @@ class WP_Job_Manager_Writepanels {
731
  } else {
732
  update_post_meta( $post_id, $key, sanitize_text_field( $_POST[ $key ] ) );
733
  }
734
- break;
735
  }
736
  }
737
  }
738
-
739
- /* Set Post Status To Expired If Already Expired */
740
- $expiry_date = get_post_meta( $post_id, '_job_expires', true );
741
- $today_date = date( 'Y-m-d', current_time( 'timestamp' ) );
742
- $is_job_listing_expired = $expiry_date && $today_date > $expiry_date;
743
- if ( $is_job_listing_expired && ! $this->is_job_listing_status_changing( null, 'draft' ) ) {
744
- remove_action( 'job_manager_save_job_listing', array( $this, 'save_job_listing_data' ), 20, 2 );
745
- if ( $this->is_job_listing_status_changing( 'expired', 'publish' ) ) {
746
- update_post_meta( $post_id, '_job_expires', calculate_job_expiry( $post_id ) );
747
- } else {
748
- $job_data = array(
749
- 'ID' => $post_id,
750
- 'post_status' => 'expired',
751
- );
752
- wp_update_post( $job_data );
753
- }
754
- add_action( 'job_manager_save_job_listing', array( $this, 'save_job_listing_data' ), 20, 2 );
755
- }
756
- }
757
-
758
- /**
759
- * Checks if the job listing status is being changed from $from_status to $to_status.
760
- *
761
- * @param string|null $from_status Status to test if it is changing from. NULL if anything.
762
- * @param string $to_status Status to test if it is changing to.
763
- *
764
- * @return bool True if status is changing from $from_status to $to_status.
765
- */
766
- private function is_job_listing_status_changing( $from_status, $to_status ) {
767
- return isset( $_POST['post_status'] )
768
- && isset( $_POST['original_post_status'] )
769
- && $_POST['original_post_status'] !== $_POST['post_status']
770
- && (
771
- null === $from_status
772
- || $from_status === $_POST['original_post_status']
773
- )
774
- && $to_status === $_POST['post_status'];
775
  }
776
  }
777
 
778
- WP_Job_Manager_Writepanels::instance();
1
  <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
3
 
 
 
 
 
 
 
4
  class WP_Job_Manager_Writepanels {
5
 
6
  /**
7
+ * __construct function.
8
  *
9
+ * @access public
10
+ * @return void
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  */
12
  public function __construct() {
13
  add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) );
16
  }
17
 
18
  /**
19
+ * job_listing_fields function.
20
  *
21
+ * @access public
22
+ * @return void
23
  */
24
  public function job_listing_fields() {
25
  global $post;
27
  $current_user = wp_get_current_user();
28
 
29
  $fields = array(
30
+ '_job_location' => array(
31
+ 'label' => __( 'Location', 'wp-job-manager' ),
32
  'placeholder' => __( 'e.g. "London"', 'wp-job-manager' ),
33
  'description' => __( 'Leave this blank if the location is not important.', 'wp-job-manager' ),
34
+ 'priority' => 1
35
  ),
36
+ '_application' => array(
37
  'label' => __( 'Application Email or URL', 'wp-job-manager' ),
38
  'placeholder' => __( 'URL or email which applicants use to apply', 'wp-job-manager' ),
39
  'description' => __( 'This field is required for the "application" area to appear beneath the listing.', 'wp-job-manager' ),
40
  'value' => metadata_exists( 'post', $post->ID, '_application' ) ? get_post_meta( $post->ID, '_application', true ) : $current_user->user_email,
41
+ 'priority' => 2
42
  ),
43
+ '_company_name' => array(
44
  'label' => __( 'Company Name', 'wp-job-manager' ),
45
  'placeholder' => '',
46
+ 'priority' => 3
47
  ),
48
  '_company_website' => array(
49
  'label' => __( 'Company Website', 'wp-job-manager' ),
50
  'placeholder' => '',
51
+ 'priority' => 4
52
  ),
53
  '_company_tagline' => array(
54
  'label' => __( 'Company Tagline', 'wp-job-manager' ),
55
  'placeholder' => __( 'Brief description about the company', 'wp-job-manager' ),
56
+ 'priority' => 5
57
  ),
58
  '_company_twitter' => array(
59
  'label' => __( 'Company Twitter', 'wp-job-manager' ),
60
  'placeholder' => '@yourcompany',
61
+ 'priority' => 6
62
  ),
63
+ '_company_video' => array(
64
  'label' => __( 'Company Video', 'wp-job-manager' ),
65
  'placeholder' => __( 'URL to the company video', 'wp-job-manager' ),
66
  'type' => 'file',
67
+ 'priority' => 8
68
  ),
69
+ '_filled' => array(
70
  'label' => __( 'Position Filled', 'wp-job-manager' ),
71
  'type' => 'checkbox',
72
  'priority' => 9,
73
  'description' => __( 'Filled listings will no longer accept applications.', 'wp-job-manager' ),
74
+ )
75
  );
76
  if ( $current_user->has_cap( 'manage_job_listings' ) ) {
77
+ $fields['_featured'] = array(
78
  'label' => __( 'Featured Listing', 'wp-job-manager' ),
79
  'type' => 'checkbox',
80
  'description' => __( 'Featured listings will be sticky during searches, and can be styled differently.', 'wp-job-manager' ),
81
+ 'priority' => 10
82
  );
 
83
  $fields['_job_expires'] = array(
84
  'label' => __( 'Listing Expiry Date', 'wp-job-manager' ),
85
  'priority' => 11,
86
+ 'placeholder' => _x( 'yyyy-mm-dd', 'Date format placeholder', 'wp-job-manager' ),
87
+ 'value' => metadata_exists( 'post', $post->ID, '_job_expires' ) ? get_post_meta( $post->ID, '_job_expires', true ) : calculate_job_expiry( $post->ID ),
 
88
  );
89
  }
90
  if ( $current_user->has_cap( 'edit_others_job_listings' ) ) {
91
  $fields['_job_author'] = array(
92
  'label' => __( 'Posted by', 'wp-job-manager' ),
93
  'type' => 'author',
94
+ 'priority' => 12
95
  );
96
  }
97
 
98
+ $fields = apply_filters( 'job_manager_job_listing_data_fields', $fields );
 
 
 
 
 
 
 
 
 
99
 
100
  uasort( $fields, array( $this, 'sort_by_priority' ) );
101
 
103
  }
104
 
105
  /**
106
+ * Sort array by priority value
 
 
 
 
107
  */
108
  protected function sort_by_priority( $a, $b ) {
109
+ if ( ! isset( $a['priority'] ) || ! isset( $b['priority'] ) || $a['priority'] === $b['priority'] ) {
110
+ return 0;
111
+ }
112
+ return ( $a['priority'] < $b['priority'] ) ? -1 : 1;
113
  }
114
 
115
  /**
116
+ * add_meta_boxes function.
117
+ *
118
+ * @access public
119
+ * @return void
120
  */
121
  public function add_meta_boxes() {
122
  global $wp_post_types;
123
 
 
124
  add_meta_box( 'job_listing_data', sprintf( __( '%s Data', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->singular_name ), array( $this, 'job_listing_data' ), 'job_listing', 'normal', 'high' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  }
126
 
127
  /**
128
+ * input_file function.
129
  *
130
+ * @param mixed $key
131
+ * @param mixed $field
132
  */
133
  public static function input_file( $key, $field ) {
134
  global $thepostid;
146
  }
147
  ?>
148
  <p class="form-field">
149
+ <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $field['label'] ) ; ?>: <?php if ( ! empty( $field['description'] ) ) : ?><span class="tips" data-tip="<?php echo esc_attr( $field['description'] ); ?>">[?]</span><?php endif; ?></label>
 
 
 
 
150
  <?php
151
  if ( ! empty( $field['multiple'] ) ) {
152
  foreach ( (array) $field['value'] as $value ) {
153
+ ?><span class="file_url"><input type="text" name="<?php echo esc_attr( $name ); ?>[]" placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" value="<?php echo esc_attr( $value ); ?>" /><button class="button button-small wp_job_manager_upload_file_button" data-uploader_button_text="<?php _e( 'Use file', 'wp-job-manager' ); ?>"><?php _e( 'Upload', 'wp-job-manager' ); ?></button></span><?php
 
 
154
  }
155
  } else {
156
+ ?><span class="file_url"><input type="text" name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $key ); ?>" placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" value="<?php echo esc_attr( $field['value'] ); ?>" /><button class="button button-small wp_job_manager_upload_file_button" data-uploader_button_text="<?php _e( 'Use file', 'wp-job-manager' ); ?>"><?php _e( 'Upload', 'wp-job-manager' ); ?></button></span><?php
 
 
157
  }
158
  if ( ! empty( $field['multiple'] ) ) {
159
+ ?><button class="button button-small wp_job_manager_add_another_file_button" data-field_name="<?php echo esc_attr( $key ); ?>" data-field_placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" data-uploader_button_text="<?php _e( 'Use file', 'wp-job-manager' ); ?>" data-uploader_button="<?php _e( 'Upload', 'wp-job-manager' ); ?>"><?php _e( 'Add file', 'wp-job-manager' ); ?></button><?php
 
 
160
  }
161
  ?>
162
  </p>
164
  }
165
 
166
  /**
167
+ * input_text function.
168
  *
169
+ * @param mixed $key
170
+ * @param mixed $field
171
  */
172
  public static function input_text( $key, $field ) {
173
  global $thepostid;
180
  } else {
181
  $name = $key;
182
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  ?>
184
  <p class="form-field">
185
+ <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $field['label'] ) ; ?>: <?php if ( ! empty( $field['description'] ) ) : ?><span class="tips" data-tip="<?php echo esc_attr( $field['description'] ); ?>">[?]</span><?php endif; ?></label>
186
+ <input type="text" name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $key ); ?>" placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>" value="<?php echo esc_attr( $field['value'] ); ?>" />
 
 
 
 
 
 
 
187
  </p>
188
  <?php
189
  }
190
 
191
  /**
192
+ * input_text function.
193
  *
194
+ * @param mixed $key
195
+ * @param mixed $field
196
  */
197
  public static function input_textarea( $key, $field ) {
198
  global $thepostid;
207
  }
208
  ?>
209
  <p class="form-field">
210
+ <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $field['label'] ) ; ?>: <?php if ( ! empty( $field['description'] ) ) : ?><span class="tips" data-tip="<?php echo esc_attr( $field['description'] ); ?>">[?]</span><?php endif; ?></label>
 
 
 
 
211
  <textarea name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $key ); ?>" placeholder="<?php echo esc_attr( $field['placeholder'] ); ?>"><?php echo esc_html( $field['value'] ); ?></textarea>
212
  </p>
213
  <?php
214
  }
215
 
216
  /**
217
+ * input_select function.
218
  *
219
+ * @param mixed $key
220
+ * @param mixed $field
221
  */
222
  public static function input_select( $key, $field ) {
223
  global $thepostid;
232
  }
233
  ?>
234
  <p class="form-field">
235
+ <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $field['label'] ) ; ?>: <?php if ( ! empty( $field['description'] ) ) : ?><span class="tips" data-tip="<?php echo esc_attr( $field['description'] ); ?>">[?]</span><?php endif; ?></label>
 
 
 
 
 
236
  <select name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $key ); ?>">
237
  <?php foreach ( $field['options'] as $key => $value ) : ?>
238
+ <option value="<?php echo esc_attr( $key ); ?>" <?php if ( isset( $field['value'] ) ) selected( $field['value'], $key ); ?>><?php echo esc_html( $value ); ?></option>
 
 
 
 
 
 
 
239
  <?php endforeach; ?>
240
  </select>
241
  </p>
243
  }
244
 
245
  /**
246
+ * input_select function.
247
  *
248
+ * @param mixed $key
249
+ * @param mixed $field
250
  */
251
  public static function input_multiselect( $key, $field ) {
252
  global $thepostid;
261
  }
262
  ?>
263
  <p class="form-field">
264
+ <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $field['label'] ) ; ?>: <?php if ( ! empty( $field['description'] ) ) : ?><span class="tips" data-tip="<?php echo esc_attr( $field['description'] ); ?>">[?]</span><?php endif; ?></label>
 
 
 
 
265
  <select multiple="multiple" name="<?php echo esc_attr( $name ); ?>[]" id="<?php echo esc_attr( $key ); ?>">
266
  <?php foreach ( $field['options'] as $key => $value ) : ?>
267
+ <option value="<?php echo esc_attr( $key ); ?>" <?php if ( ! empty( $field['value'] ) && is_array( $field['value'] ) ) selected( in_array( $key, $field['value'] ), true ); ?>><?php echo esc_html( $value ); ?></option>
 
 
 
 
 
 
 
268
  <?php endforeach; ?>
269
  </select>
270
  </p>
272
  }
273
 
274
  /**
275
+ * input_checkbox function.
276
  *
277
+ * @param mixed $key
278
+ * @param mixed $field
279
  */
280
  public static function input_checkbox( $key, $field ) {
281
  global $thepostid;
290
  }
291
  ?>
292
  <p class="form-field form-field-checkbox">
293
+ <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $field['label'] ) ; ?></label>
294
  <input type="checkbox" class="checkbox" name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $key ); ?>" value="1" <?php checked( $field['value'], 1 ); ?> />
295
+ <?php if ( ! empty( $field['description'] ) ) : ?><span class="description"><?php echo $field['description']; ?></span><?php endif; ?>
 
 
296
  </p>
297
  <?php
298
  }
299
 
300
  /**
301
+ * Box to choose who posted the job
302
  *
303
+ * @param mixed $key
304
+ * @param mixed $field
305
  */
306
  public static function input_author( $key, $field ) {
307
  global $thepostid, $post;
318
  $name = ! empty( $field['name'] ) ? $field['name'] : $key;
319
  ?>
320
  <p class="form-field form-field-author">
321
+ <label for="<?php echo esc_attr( $key ); ?>"><?php echo esc_html( $field['label'] ) ; ?>:</label>
322
  <span class="current-author">
323
  <?php
324
+ if ( $posted_by ) {
325
+ echo '<a href="' . admin_url( 'user-edit.php?user_id=' . absint( $author_id ) ) . '">#' . absint( $author_id ) . ' &ndash; ' . $posted_by->user_login . '</a>';
326
+ } else {
327
+ _e( 'Guest User', 'wp-job-manager' );
328
+ }
329
+ ?> <a href="#" class="change-author button button-small"><?php _e( 'Change', 'wp-job-manager' ); ?></a>
 
330
  </span>
331
  <span class="hidden change-author">
332
  <input type="number" name="<?php echo esc_attr( $name ); ?>" id="<?php echo esc_attr( $key ); ?>" step="1" value="<?php echo esc_attr( $author_id ); ?>" style="width: 4em;" />
333
+ <span class="description"><?php _e( 'Enter the ID of the user, or leave blank if submitted by a guest.', 'wp-job-manager' ) ?></span>
334
  </span>
335
  </p>
336
  <?php
337
  }
338
 
339
  /**
340
+ * input_radio function.
341
  *
342
+ * @param mixed $key
343
+ * @param mixed $field
344
  */
345
  public static function input_radio( $key, $field ) {
346
  global $thepostid;
355
  }
356
  ?>
357
  <p class="form-field form-field-checkbox">
358
+ <label><?php echo esc_html( $field['label'] ) ; ?></label>
359
  <?php foreach ( $field['options'] as $option_key => $value ) : ?>
360
  <label><input type="radio" class="radio" name="<?php echo esc_attr( isset( $field['name'] ) ? $field['name'] : $key ); ?>" value="<?php echo esc_attr( $option_key ); ?>" <?php checked( $field['value'], $option_key ); ?> /> <?php echo esc_html( $value ); ?></label>
361
  <?php endforeach; ?>
362
+ <?php if ( ! empty( $field['description'] ) ) : ?><span class="description"><?php echo $field['description']; ?></span><?php endif; ?>
 
 
363
  </p>
364
  <?php
365
  }
366
 
367
  /**
368
+ * job_listing_data function.
369
  *
370
+ * @access public
371
+ * @param mixed $post
372
+ * @return void
373
  */
374
  public function job_listing_data( $post ) {
375
+ global $post, $thepostid;
376
 
377
  $thepostid = $post->ID;
378
 
392
  }
393
  }
394
 
 
 
 
 
 
 
 
 
395
  do_action( 'job_manager_job_listing_data_end', $thepostid );
396
 
397
  echo '</div>';
398
  }
399
 
400
  /**
401
+ * save_post function.
402
  *
403
+ * @access public
404
+ * @param mixed $post_id
405
+ * @param mixed $post
406
+ * @return void
407
  */
408
  public function save_post( $post_id, $post ) {
409
+ if ( empty( $post_id ) || empty( $post ) || empty( $_POST ) ) return;
410
+ if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) return;
411
+ if ( is_int( wp_is_post_revision( $post ) ) ) return;
412
+ if ( is_int( wp_is_post_autosave( $post ) ) ) return;
413
+ if ( empty($_POST['job_manager_nonce']) || ! wp_verify_nonce( $_POST['job_manager_nonce'], 'save_meta_data' ) ) return;
414
+ if ( ! current_user_can( 'edit_post', $post_id ) ) return;
415
+ if ( $post->post_type != 'job_listing' ) return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
 
417
  do_action( 'job_manager_save_job_listing', $post_id, $post );
418
  }
419
 
420
  /**
421
+ * save_job_listing_data function.
422
  *
423
+ * @access public
424
+ * @param mixed $post_id
425
+ * @param mixed $post
426
+ * @return void
427
  */
428
  public function save_job_listing_data( $post_id, $post ) {
429
  global $wpdb;
430
 
431
+ // These need to exist
432
  add_post_meta( $post_id, '_filled', 0, true );
433
  add_post_meta( $post_id, '_featured', 0, true );
434
 
435
+ // Save fields
436
  foreach ( $this->job_listing_fields() as $key => $field ) {
437
+ // Expirey date
 
 
 
 
438
  if ( '_job_expires' === $key ) {
439
+ if ( ! empty( $_POST[ $key ] ) ) {
 
 
 
 
 
 
440
  update_post_meta( $post_id, $key, date( 'Y-m-d', strtotime( sanitize_text_field( $_POST[ $key ] ) ) ) );
441
+ } else {
442
+ update_post_meta( $post_id, $key, '' );
443
  }
444
+ }
445
+
446
+ // Locations
447
+ elseif ( '_job_location' === $key ) {
448
+ if ( update_post_meta( $post_id, $key, sanitize_text_field( $_POST[ $key ] ) ) ) {
449
+ // Location data will be updated by hooked in methods
450
+ } elseif ( apply_filters( 'job_manager_geolocation_enabled', true ) && ! WP_Job_Manager_Geocode::has_location_data( $post_id ) ) {
451
  WP_Job_Manager_Geocode::generate_location_data( $post_id, sanitize_text_field( $_POST[ $key ] ) );
452
  }
453
+ }
454
+
455
+ elseif ( '_job_author' === $key ) {
456
  $wpdb->update( $wpdb->posts, array( 'post_author' => $_POST[ $key ] > 0 ? absint( $_POST[ $key ] ) : 0 ), array( 'ID' => $post_id ) );
457
+ }
458
+
459
+ elseif ( '_application' === $key ) {
460
+ update_post_meta( $post_id, $key, sanitize_text_field( urldecode( $_POST[ $key ] ) ) );
461
+ }
462
+
463
+ // Everything else
464
+ else {
465
  $type = ! empty( $field['type'] ) ? $field['type'] : '';
466
 
467
  switch ( $type ) {
468
+ case 'textarea' :
469
  update_post_meta( $post_id, $key, wp_kses_post( stripslashes( $_POST[ $key ] ) ) );
470
+ break;
471
+ case 'checkbox' :
472
  if ( isset( $_POST[ $key ] ) ) {
473
  update_post_meta( $post_id, $key, 1 );
474
  } else {
475
  update_post_meta( $post_id, $key, 0 );
476
  }
477
+ break;
478
+ default :
479
  if ( ! isset( $_POST[ $key ] ) ) {
480
  continue;
481
  } elseif ( is_array( $_POST[ $key ] ) ) {
483
  } else {
484
  update_post_meta( $post_id, $key, sanitize_text_field( $_POST[ $key ] ) );
485
  }
486
+ break;
487
  }
488
  }
489
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
  }
491
  }
492
 
493
+ new WP_Job_Manager_Writepanels();
includes/admin/views/html-admin-page-addons.php DELETED
@@ -1,72 +0,0 @@
1
- <?php
2
- echo '<h1 class="screen-reader-text">' . esc_html__( 'WP Job Manager Add-ons', 'wp-job-manager' ) . '</h1>';
3
- if ( ! empty( $messages ) ) {
4
- foreach ( $messages as $message ) {
5
- if ( empty( $message->message ) ) {
6
- continue;
7
- }
8
- $type = 'info';
9
- if ( isset( $message->type )
10
- && in_array( $message->type, array( 'info', 'success', 'warning', 'error' ), true ) ) {
11
- $type = $message->type;
12
- }
13
- $action_label = isset( $message->action_label ) ? esc_attr( $message->action_label ) : __( 'More Information &rarr;', 'wp-job-manager' );
14
- $action_url = isset( $message->action_url ) ? esc_url( $message->action_url, array( 'http', 'https' ) ) : false;
15
- $action_target = isset( $message->action_target ) && 'self' === $message->action_target ? '_self' : '_blank';
16
- $action_str = '';
17
- if ( $action_url ) {
18
- $action_str = ' <a href="' . esc_url( $action_url ) . '" target="' . esc_attr( $action_target ) . '" class="button">' . esc_html( $action_label ) . '</a>';
19
- }
20
-
21
- echo '<div class="notice notice-' . esc_attr( $type ) . ' below-h2"><p><strong>' . esc_html( $message->message ) . '</strong>' . wp_kses_post( $action_str ) . '</p></div>';
22
- }
23
- }
24
- if ( ! empty( $categories ) ) {
25
- $current_category = isset( $_GET['category'] ) ? $_GET['category'] : '_all';
26
- echo '<ul class="subsubsub">';
27
- foreach ( $categories as $category ) {
28
- ?>
29
- <li>
30
- <a class="<?php echo $current_category === $category->slug ? 'current' : ''; ?>"
31
- href="<?php echo esc_url( admin_url( 'edit.php?post_type=job_listing&page=job-manager-addons&category=' . esc_attr( $category->slug ) ) ); ?>">
32
- <?php echo esc_html( $category->label ); ?>
33
- </a>
34
- </li>
35
- <?php
36
- }
37
- echo '</ul>';
38
- }
39
-
40
- echo '<br class="clear" />';
41
-
42
- if ( empty( $add_ons ) ) {
43
- echo '<div class="notice notice-warning below-h2"><p><strong>' . esc_html__( 'No add-ons were found.', 'wp-job-manager' ) . '</strong></p></div>';
44
- } else {
45
- echo '<ul class="products">';
46
- foreach ( $add_ons as $add_on ) {
47
- $url = add_query_arg(
48
- array(
49
- 'utm_source' => 'product',
50
- 'utm_medium' => 'addonpage',
51
- 'utm_campaign' => 'wpjmplugin',
52
- 'utm_content' => 'listing',
53
- ), $add_on->link
54
- );
55
- ?>
56
- <li class="product">
57
- <a href="<?php echo esc_url( $url, array( 'http', 'https' ) ); ?>">
58
- <?php if ( ! empty( $add_on->image ) ) : ?>
59
- <img src="<?php echo esc_url( $add_on->image ); ?>" />
60
- <?php endif; ?>
61
- <h2><?php echo esc_html( $add_on->title ); ?></h2>
62
- <p><?php echo esc_html( $add_on->excerpt ); ?>
63
- <?php if ( ! empty( $add_on->price ) ) : ?>
64
- <span class="price"><?php echo esc_html( $add_on->price ); ?></span>
65
- <?php endif; ?>
66
- </p>
67
- </a>
68
- </li>
69
- <?php
70
- }
71
- echo '</ul>';
72
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/class-wp-job-manager-ajax.php CHANGED
@@ -1,51 +1,24 @@
1
  <?php
2
 
3
- if ( ! defined( 'ABSPATH' ) ) {
4
- exit; // Exit if accessed directly.
5
- }
6
 
7
  /**
8
- * Handles Job Manager's Ajax endpoints.
9
- *
10
- * @package wp-job-manager
11
- * @since 1.0.0
12
  */
13
  class WP_Job_Manager_Ajax {
14
 
15
  /**
16
- * The single instance of the class.
17
- *
18
- * @var self
19
- * @since 1.26.0
20
- */
21
- private static $_instance = null;
22
-
23
- /**
24
- * Allows for accessing single instance of class. Class should only be constructed once per call.
25
- *
26
- * @since 1.26.0
27
- * @static
28
- * @return self Main instance.
29
- */
30
- public static function instance() {
31
- if ( is_null( self::$_instance ) ) {
32
- self::$_instance = new self();
33
- }
34
- return self::$_instance;
35
- }
36
-
37
- /**
38
- * Constructor.
39
  */
40
  public function __construct() {
41
- add_action( 'init', array( __CLASS__, 'add_endpoint' ) );
42
- add_action( 'template_redirect', array( __CLASS__, 'do_jm_ajax' ), 0 );
43
 
44
- // JM Ajax endpoints.
45
  add_action( 'job_manager_ajax_get_listings', array( $this, 'get_listings' ) );
46
  add_action( 'job_manager_ajax_upload_file', array( $this, 'upload_file' ) );
47
 
48
- // BW compatible handlers.
49
  add_action( 'wp_ajax_nopriv_job_manager_get_listings', array( $this, 'get_listings' ) );
50
  add_action( 'wp_ajax_job_manager_get_listings', array( $this, 'get_listings' ) );
51
  add_action( 'wp_ajax_nopriv_job_manager_upload_file', array( $this, 'upload_file' ) );
@@ -53,7 +26,7 @@ class WP_Job_Manager_Ajax {
53
  }
54
 
55
  /**
56
- * Adds endpoint for frontend Ajax requests.
57
  */
58
  public static function add_endpoint() {
59
  add_rewrite_tag( '%jm-ajax%', '([^/]*)' );
@@ -62,10 +35,9 @@ class WP_Job_Manager_Ajax {
62
  }
63
 
64
  /**
65
- * Gets Job Manager's Ajax Endpoint.
66
- *
67
- * @param string $request Optional.
68
- * @param string $ssl (Unused) Optional.
69
  * @return string
70
  */
71
  public static function get_endpoint( $request = '%%endpoint%%', $ssl = null ) {
@@ -80,7 +52,7 @@ class WP_Job_Manager_Ajax {
80
  }
81
 
82
  /**
83
- * Performs Job Manager's Ajax actions.
84
  */
85
  public static function do_jm_ajax() {
86
  global $wp_query;
@@ -89,41 +61,33 @@ class WP_Job_Manager_Ajax {
89
  $wp_query->set( 'jm-ajax', sanitize_text_field( $_GET['jm-ajax'] ) );
90
  }
91
 
92
- $action = $wp_query->get( 'jm-ajax' );
93
- if ( $action ) {
94
- if ( ! defined( 'DOING_AJAX' ) ) {
95
  define( 'DOING_AJAX', true );
96
  }
97
 
98
- // Not home - this is an ajax endpoint.
99
  $wp_query->is_home = false;
100
 
101
- /**
102
- * Performs an Ajax action.
103
- * The dynamic part of the action, $action, is the predefined Ajax action to be performed.
104
- *
105
- * @since 1.23.0
106
- */
107
- do_action( 'job_manager_ajax_' . sanitize_text_field( $action ) );
108
- wp_die();
109
- }
110
  }
111
 
112
  /**
113
- * Returns Job Listings for Ajax endpoint.
114
  */
115
  public function get_listings() {
116
  global $wp_post_types;
117
 
118
- $result = array();
119
- $search_location = sanitize_text_field( stripslashes( $_REQUEST['search_location'] ) );
120
- $search_keywords = sanitize_text_field( stripslashes( $_REQUEST['search_keywords'] ) );
121
- $search_categories = isset( $_REQUEST['search_categories'] ) ? $_REQUEST['search_categories'] : '';
122
- $filter_job_types = isset( $_REQUEST['filter_job_type'] ) ? array_filter( array_map( 'sanitize_title', (array) $_REQUEST['filter_job_type'] ) ) : null;
123
- $filter_post_status = isset( $_REQUEST['filter_post_status'] ) ? array_filter( array_map( 'sanitize_title', (array) $_REQUEST['filter_post_status'] ) ) : null;
124
- $types = get_job_listing_types();
125
- $post_type_label = $wp_post_types['job_listing']->labels->name;
126
- $orderby = sanitize_text_field( $_REQUEST['orderby'] );
127
 
128
  if ( is_array( $search_categories ) ) {
129
  $search_categories = array_filter( array_map( 'sanitize_text_field', array_map( 'stripslashes', $search_categories ) ) );
@@ -132,161 +96,131 @@ class WP_Job_Manager_Ajax {
132
  }
133
 
134
  $args = array(
135
- 'search_location' => $search_location,
136
- 'search_keywords' => $search_keywords,
137
- 'search_categories' => $search_categories,
138
- 'job_types' => is_null( $filter_job_types ) || count( $types ) === count( $filter_job_types ) ? '' : $filter_job_types + array( 0 ),
139
- 'post_status' => $filter_post_status,
140
- 'orderby' => $orderby,
141
- 'order' => sanitize_text_field( $_REQUEST['order'] ),
142
- 'offset' => ( absint( $_REQUEST['page'] ) - 1 ) * absint( $_REQUEST['per_page'] ),
143
- 'posts_per_page' => max( 1, absint( $_REQUEST['per_page'] ) ),
144
  );
145
 
146
- if ( isset( $_REQUEST['filled'] ) && ( 'true' === $_REQUEST['filled'] || 'false' === $_REQUEST['filled'] ) ) {
147
- $args['filled'] = 'true' === $_REQUEST['filled'];
148
  }
149
 
150
- if ( isset( $_REQUEST['featured'] ) && ( 'true' === $_REQUEST['featured'] || 'false' === $_REQUEST['featured'] ) ) {
151
- $args['featured'] = 'true' === $_REQUEST['featured'];
152
  $args['orderby'] = 'featured' === $orderby ? 'date' : $orderby;
153
  }
154
 
155
- /**
156
- * Get the arguments to use when building the Job Listing WP Query.
157
- *
158
- * @since 1.0.0
159
- *
160
- * @param array $args Arguments used for generating Job Listing query (see `get_job_listings()`).
161
- */
162
  $jobs = get_job_listings( apply_filters( 'job_manager_get_listings_args', $args ) );
163
 
164
- $result = array(
165
- 'found_jobs' => $jobs->have_posts(),
166
- 'showing' => '',
167
- 'max_num_pages' => $jobs->max_num_pages,
168
- );
169
 
170
- if ( $jobs->post_count && ( $search_location || $search_keywords || $search_categories ) ) {
171
- // translators: Placeholder %d is the number of found search results.
172
- $message = sprintf( _n( 'Search completed. Found %d matching record.', 'Search completed. Found %d matching records.', $jobs->found_posts, 'wp-job-manager' ), $jobs->found_posts );
173
- $result['showing_all'] = true;
174
- } else {
175
- $message = '';
176
- }
177
 
178
- $search_values = array(
179
- 'location' => $search_location,
180
- 'keywords' => $search_keywords,
181
- 'categories' => $search_categories,
182
- );
183
 
184
- /**
185
- * Filter the message that describes the results of the search query.
186
- *
187
- * @since 1.0.0
188
- *
189
- * @param string $message Default message that is generated when posts are found.
190
- * @param array $search_values {
191
- * Helpful values often used in the generation of this message.
192
- *
193
- * @type string $location Query used to filter by job listing location.
194
- * @type string $keywords Query used to filter by general keywords.
195
- * @type array $categories List of the categories to filter by.
196
- * }
197
- */
198
- $result['showing'] = apply_filters( 'job_manager_get_listings_custom_filter_text', $message, $search_values );
199
-
200
- // Generate RSS link.
201
- $result['showing_links'] = job_manager_get_filtered_links(
202
- array(
203
- 'filter_job_types' => $filter_job_types,
204
- 'search_location' => $search_location,
205
- 'search_categories' => $search_categories,
206
- 'search_keywords' => $search_keywords,
207
- )
208
- );
209
 
210
- /**
211
- * Send back a response to the AJAX request without creating HTML.
212
- *
213
- * @since 1.26.0
214
- *
215
- * @param array $result
216
- * @param WP_Query $jobs
217
- * @return bool True by default. Change to false to halt further response.
218
- */
219
- if ( true !== apply_filters( 'job_manager_ajax_get_jobs_html_results', true, $result, $jobs ) ) {
220
- /**
221
- * Filters the results of the job listing Ajax query to be sent back to the client.
222
- *
223
- * @since 1.0.0
224
- *
225
- * @param array $result {
226
- * Package of the query results along with meta information.
227
- *
228
- * @type bool $found_jobs Whether or not jobs were found in the query.
229
- * @type string $showing Description of the search query and results.
230
- * @type int $max_num_pages Number of pages in the search result.
231
- * @type string $html HTML representation of the search results (only if filter
232
- * `job_manager_ajax_get_jobs_html_results` returns true).
233
- * @type array $pagination Pagination links to use for stepping through filter results.
234
- * }
235
- */
236
- return wp_send_json( apply_filters( 'job_manager_get_listings_result', $result, $jobs ) );
237
  }
238
 
239
- ob_start();
 
 
 
 
 
 
 
 
240
 
241
- if ( $result['found_jobs'] ) {
242
- while ( $jobs->have_posts() ) {
243
- $jobs->the_post();
244
- get_job_manager_template_part( 'content', 'job_listing' );
 
 
245
  }
246
- } else {
247
- get_job_manager_template_part( 'content', 'no-jobs-found' );
 
 
 
 
248
  }
249
 
250
- $result['html'] = ob_get_clean();
251
 
252
- // Generate pagination.
253
- if ( isset( $_REQUEST['show_pagination'] ) && 'true' === $_REQUEST['show_pagination'] ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  $result['pagination'] = get_job_listing_pagination( $jobs->max_num_pages, absint( $_REQUEST['page'] ) );
255
  }
256
 
257
- /** This filter is documented in includes/class-wp-job-manager-ajax.php (above) */
 
258
  wp_send_json( apply_filters( 'job_manager_get_listings_result', $result, $jobs ) );
259
  }
260
 
261
  /**
262
- * Uploads file from an Ajax request.
263
  *
264
  * No nonce field since the form may be statically cached.
265
  */
266
  public function upload_file() {
267
- if ( ! job_manager_user_can_upload_file_via_ajax() ) {
268
- wp_send_json_error( __( 'You must be logged in to upload files using this method.', 'wp-job-manager' ) );
269
- return;
270
- }
271
- $data = array(
272
- 'files' => array(),
273
- );
274
 
275
  if ( ! empty( $_FILES ) ) {
276
  foreach ( $_FILES as $file_key => $file ) {
277
  $files_to_upload = job_manager_prepare_uploaded_files( $file );
278
  foreach ( $files_to_upload as $file_to_upload ) {
279
- $uploaded_file = job_manager_upload_file(
280
- $file_to_upload,
281
- array(
282
- 'file_key' => $file_key,
283
- )
284
- );
285
 
286
  if ( is_wp_error( $uploaded_file ) ) {
287
- $data['files'][] = array(
288
- 'error' => $uploaded_file->get_error_message(),
289
- );
290
  } else {
291
  $data['files'][] = $uploaded_file;
292
  }
@@ -298,4 +232,4 @@ class WP_Job_Manager_Ajax {
298
  }
299
  }
300
 
301
- WP_Job_Manager_Ajax::instance();
1
  <?php
2
 
3
+ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
4
 
5
  /**
6
+ * WP_Job_Manager_Ajax class.
 
 
 
7
  */
8
  class WP_Job_Manager_Ajax {
9
 
10
  /**
11
+ * Constructor
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  */
13
  public function __construct() {
14
+ add_action( 'init', array( __CLASS__, 'add_endpoint') );
15
+ add_action( 'template_redirect', array( __CLASS__, 'do_jm_ajax'), 0 );
16
 
17
+ // JM Ajax endpoints
18
  add_action( 'job_manager_ajax_get_listings', array( $this, 'get_listings' ) );
19
  add_action( 'job_manager_ajax_upload_file', array( $this, 'upload_file' ) );
20
 
21
+ // BW compatible handlers
22
  add_action( 'wp_ajax_nopriv_job_manager_get_listings', array( $this, 'get_listings' ) );
23
  add_action( 'wp_ajax_job_manager_get_listings', array( $this, 'get_listings' ) );
24
  add_action( 'wp_ajax_nopriv_job_manager_upload_file', array( $this, 'upload_file' ) );
26
  }
27
 
28
  /**
29
+ * Add our endpoint for frontend ajax requests
30
  */
31
  public static function add_endpoint() {
32
  add_rewrite_tag( '%jm-ajax%', '([^/]*)' );
35
  }
36
 
37
  /**
38
+ * Get JM Ajax Endpoint
39
+ * @param string $request Optional
40
+ * @param string $ssl Optional
 
41
  * @return string
42
  */
43
  public static function get_endpoint( $request = '%%endpoint%%', $ssl = null ) {
52
  }
53
 
54
  /**
55
+ * Check for WC Ajax request and fire action
56
  */
57
  public static function do_jm_ajax() {
58
  global $wp_query;
61
  $wp_query->set( 'jm-ajax', sanitize_text_field( $_GET['jm-ajax'] ) );
62
  }
63
 
64
+ if ( $action = $wp_query->get( 'jm-ajax' ) ) {
65
+ if ( ! defined( 'DOING_AJAX' ) ) {
 
66
  define( 'DOING_AJAX', true );
67
  }
68
 
69
+ // Not home - this is an ajax endpoint
70
  $wp_query->is_home = false;
71
 
72
+ do_action( 'job_manager_ajax_' . sanitize_text_field( $action ) );
73
+ die();
74
+ }
 
 
 
 
 
 
75
  }
76
 
77
  /**
78
+ * Get listings via ajax
79
  */
80
  public function get_listings() {
81
  global $wp_post_types;
82
 
83
+ $result = array();
84
+ $search_location = sanitize_text_field( stripslashes( $_REQUEST['search_location'] ) );
85
+ $search_keywords = sanitize_text_field( stripslashes( $_REQUEST['search_keywords'] ) );
86
+ $search_categories = isset( $_REQUEST['search_categories'] ) ? $_REQUEST['search_categories'] : '';
87
+ $filter_job_types = isset( $_REQUEST['filter_job_type'] ) ? array_filter( array_map( 'sanitize_title', (array) $_REQUEST['filter_job_type'] ) ) : null;
88
+ $types = get_job_listing_types();
89
+ $post_type_label = $wp_post_types['job_listing']->labels->name;
90
+ $orderby = sanitize_text_field( $_REQUEST['orderby'] );
 
91
 
92
  if ( is_array( $search_categories ) ) {
93
  $search_categories = array_filter( array_map( 'sanitize_text_field', array_map( 'stripslashes', $search_categories ) ) );
96
  }
97
 
98
  $args = array(
99
+ 'search_location' => $search_location,
100
+ 'search_keywords' => $search_keywords,
101
+ 'search_categories' => $search_categories,
102
+ 'job_types' => is_null( $filter_job_types ) || sizeof( $types ) === sizeof( $filter_job_types ) ? '' : $filter_job_types + array( 0 ),
103
+ 'orderby' => $orderby,
104
+ 'order' => sanitize_text_field( $_REQUEST['order'] ),
105
+ 'offset' => ( absint( $_REQUEST['page'] ) - 1 ) * absint( $_REQUEST['per_page'] ),
106
+ 'posts_per_page' => absint( $_REQUEST['per_page'] )
 
107
  );
108
 
109
+ if ( isset( $_REQUEST['filled'] ) && ( $_REQUEST['filled'] === 'true' || $_REQUEST['filled'] === 'false' ) ) {
110
+ $args['filled'] = $_REQUEST['filled'] === 'true' ? true : false;
111
  }
112
 
113
+ if ( isset( $_REQUEST['featured'] ) && ( $_REQUEST['featured'] === 'true' || $_REQUEST['featured'] === 'false' ) ) {
114
+ $args['featured'] = $_REQUEST['featured'] === 'true' ? true : false;
115
  $args['orderby'] = 'featured' === $orderby ? 'date' : $orderby;
116
  }
117
 
118
+ ob_start();
119
+
 
 
 
 
 
120
  $jobs = get_job_listings( apply_filters( 'job_manager_get_listings_args', $args ) );
121
 
122
+ $result['found_jobs'] = false;
 
 
 
 
123
 
124
+ if ( $jobs->have_posts() ) : $result['found_jobs'] = true; ?>
 
 
 
 
 
 
125
 
126
+ <?php while ( $jobs->have_posts() ) : $jobs->the_post(); ?>
 
 
 
 
127
 
128
+ <?php get_job_manager_template_part( 'content', 'job_listing' ); ?>
129
+
130
+ <?php endwhile; ?>
131
+
132
+ <?php else : ?>
133
+
134
+ <?php get_job_manager_template_part( 'content', 'no-jobs-found' ); ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
+ <?php endif;
137
+
138
+ $result['html'] = ob_get_clean();
139
+ $result['showing'] = array();
140
+
141
+ // Generate 'showing' text
142
+ $showing_types = array();
143
+ $unmatched = false;
144
+
145
+ foreach ( $types as $type ) {
146
+ if ( is_array( $filter_job_types ) && in_array( $type->slug, $filter_job_types ) ) {
147
+ $showing_types[] = $type->name;
148
+ } else {
149
+ $unmatched = true;
150
+ }
 
 
 
 
 
 
 
 
 
 
 
 
151
  }
152
 
153
+ if ( sizeof( $showing_types ) == 1 ) {
154
+ $result['showing'][] = implode( ', ', $showing_types );
155
+ } elseif ( $unmatched && $showing_types ) {
156
+ $last_type = array_pop( $showing_types );
157
+ $result['showing'][] = implode( ', ', $showing_types ) . " &amp; $last_type";
158
+ }
159
+
160
+ if ( $search_categories ) {
161
+ $showing_categories = array();
162
 
163
+ foreach ( $search_categories as $category ) {
164
+ $category_object = get_term_by( is_numeric( $category ) ? 'id' : 'slug', $category, 'job_listing_category' );
165
+
166
+ if ( ! is_wp_error( $category_object ) ) {
167
+ $showing_categories[] = $category_object->name;
168
+ }
169
  }
170
+
171
+ $result['showing'][] = implode( ', ', $showing_categories );
172
+ }
173
+
174
+ if ( $search_keywords ) {
175
+ $result['showing'][] = '&ldquo;' . $search_keywords . '&rdquo;';
176
  }
177
 
178
+ $result['showing'][] = $post_type_label;
179
 
180
+ if ( $search_location ) {
181
+ $result['showing'][] = sprintf( __( 'located in &ldquo;%s&rdquo;', 'wp-job-manager' ), $search_location );
182
+ }
183
+
184
+ if ( 1 === sizeof( $result['showing'] ) ) {
185
+ $result['showing_all'] = true;
186
+ }
187
+
188
+ $result['showing'] = apply_filters( 'job_manager_get_listings_custom_filter_text', sprintf( __( 'Showing all %s', 'wp-job-manager' ), implode( ' ', $result['showing'] ) ) );
189
+
190
+ // Generate RSS link
191
+ $result['showing_links'] = job_manager_get_filtered_links( array(
192
+ 'filter_job_types' => $filter_job_types,
193
+ 'search_location' => $search_location,
194
+ 'search_categories' => $search_categories,
195
+ 'search_keywords' => $search_keywords
196
+ ) );
197
+
198
+ // Generate pagination
199
+ if ( isset( $_REQUEST['show_pagination'] ) && $_REQUEST['show_pagination'] === 'true' ) {
200
  $result['pagination'] = get_job_listing_pagination( $jobs->max_num_pages, absint( $_REQUEST['page'] ) );
201
  }
202
 
203
+ $result['max_num_pages'] = $jobs->max_num_pages;
204
+
205
  wp_send_json( apply_filters( 'job_manager_get_listings_result', $result, $jobs ) );
206
  }
207
 
208
  /**
209
+ * Upload file via ajax
210
  *
211
  * No nonce field since the form may be statically cached.
212
  */
213
  public function upload_file() {
214
+ $data = array( 'files' => array() );
 
 
 
 
 
 
215
 
216
  if ( ! empty( $_FILES ) ) {
217
  foreach ( $_FILES as $file_key => $file ) {
218
  $files_to_upload = job_manager_prepare_uploaded_files( $file );
219
  foreach ( $files_to_upload as $file_to_upload ) {
220
+ $uploaded_file = job_manager_upload_file( $file_to_upload, array( 'file_key' => $file_key ) );
 
 
 
 
 
221
 
222
  if ( is_wp_error( $uploaded_file ) ) {
223
+ $data['files'][] = array( 'error' => $uploaded_file->get_error_message() );
 
 
224
  } else {
225
  $data['files'][] = $uploaded_file;
226
  }
232
  }
233
  }
234
 
235
+ new WP_Job_Manager_Ajax();
includes/class-wp-job-manager-api.php CHANGED
@@ -1,52 +1,30 @@
1
  <?php
2
 
3
- if ( ! defined( 'ABSPATH' ) ) {
4
- exit; // Exit if accessed directly.
5
- }
6
 
7
  /**
8
- * Handles API requests for WP Job Manager.
9
  *
10
- * @package wp-job-manager
11
- * @since 1.0.0
12
  */
13
  class WP_Job_Manager_API {
14
 
15
  /**
16
- * The single instance of the class.
17
  *
18
- * @var self
19
- * @since 1.26.0
20
- */
21
- private static $_instance = null;
22
-
23
- /**
24
- * Allows for accessing single instance of class. Class should only be constructed once per call.
25
- *
26
- * @since 1.26.0
27
- * @static
28
- * @return self Main instance.
29
- */
30
- public static function instance() {
31
- if ( is_null( self::$_instance ) ) {
32
- self::$_instance = new self();
33
- }
34
- return self::$_instance;
35
- }
36
-
37
- /**
38
- * Constructor.
39
  */
40
  public function __construct() {
41
- add_filter( 'query_vars', array( $this, 'add_query_vars' ), 0 );
42
- add_action( 'parse_request', array( $this, 'api_requests' ), 0 );
43
  }
44
 
45
  /**
46
- * Adds query vars used in API calls.
47
  *
48
- * @param array $vars the query vars.
49
- * @return array
50
  */
51
  public function add_query_vars( $vars ) {
52
  $vars[] = 'job-manager-api';
@@ -54,7 +32,10 @@ class WP_Job_Manager_API {
54
  }
55
 
56
  /**
57
- * Adds endpoint for API requests.
 
 
 
58
  */
59
  public function add_endpoint() {
60
  add_rewrite_endpoint( 'job-manager-api', EP_ALL );
@@ -62,39 +43,35 @@ class WP_Job_Manager_API {
62
 
63
  /**
64
  * API request - Trigger any API requests (handy for third party plugins/gateways).
 
 
 
65
  */
66
  public function api_requests() {
67
  global $wp;
68
 
69
- if ( ! empty( $_GET['job-manager-api'] ) ) {
70
  $wp->query_vars['job-manager-api'] = $_GET['job-manager-api'];
71
- }
72
 
73
  if ( ! empty( $wp->query_vars['job-manager-api'] ) ) {
74
- // Buffer, we won't want any output here.
75
  ob_start();
76
 
77
- // Get API trigger.
78
  $api = strtolower( esc_attr( $wp->query_vars['job-manager-api'] ) );
79
 
80
- // Load class if exists.
81
- if ( has_action( 'job_manager_api_' . $api ) && class_exists( $api ) ) {
82
  $api_class = new $api();
83
- }
84
 
85
- /**
86
- * Performs an API action.
87
- * The dynamic part of the action, $api, is the API action.
88
- *
89
- * @since 1.0.0
90
- */
91
  do_action( 'job_manager_api_' . $api );
92
 
93
- // Done, clear buffer and exit.
94
  ob_end_clean();
95
- wp_die();
96
  }
97
  }
98
  }
99
 
100
- WP_Job_Manager_API::instance();
1
  <?php
2
 
3
+ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
4
 
5
  /**
6
+ * WP_Job_Manager_API API
7
  *
8
+ * This API class handles API requests.
 
9
  */
10
  class WP_Job_Manager_API {
11
 
12
  /**
13
+ * __construct function.
14
  *
15
+ * @access public
16
+ * @return void
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  */
18
  public function __construct() {
19
+ add_filter( 'query_vars', array( $this, 'add_query_vars'), 0 );
20
+ add_action( 'parse_request', array( $this, 'api_requests'), 0 );
21
  }
22
 
23
  /**
24
+ * add_query_vars function.
25
  *
26
+ * @access public
27
+ * @return void
28
  */
29
  public function add_query_vars( $vars ) {
30
  $vars[] = 'job-manager-api';
32
  }
33
 
34
  /**
35
+ * add_endpoint function.
36
+ *
37
+ * @access public
38
+ * @return void
39
  */
40
  public function add_endpoint() {
41
  add_rewrite_endpoint( 'job-manager-api', EP_ALL );
43
 
44
  /**
45
  * API request - Trigger any API requests (handy for third party plugins/gateways).
46
+ *
47
+ * @access public
48
+ * @return void
49
  */
50
  public function api_requests() {
51
  global $wp;
52
 
53
+ if ( ! empty( $_GET['job-manager-api'] ) )
54
  $wp->query_vars['job-manager-api'] = $_GET['job-manager-api'];
 
55
 
56
  if ( ! empty( $wp->query_vars['job-manager-api'] ) ) {
57
+ // Buffer, we won't want any output here
58
  ob_start();
59
 
60
+ // Get API trigger
61
  $api = strtolower( esc_attr( $wp->query_vars['job-manager-api'] ) );
62
 
63
+ // Load class if exists
64
+ if ( class_exists( $api ) )
65
  $api_class = new $api();
 
66
 
67
+ // Trigger actions
 
 
 
 
 
68
  do_action( 'job_manager_api_' . $api );
69
 
70
+ // Done, clear buffer and exit
71
  ob_end_clean();
72
+ die('1');
73
  }
74
  }
75
  }
76
 
77
+ new WP_Job_Manager_API();
includes/class-wp-job-manager-cache-helper.php CHANGED
@@ -5,33 +5,22 @@ if ( ! defined( 'ABSPATH' ) ) {
5
  }
6
 
7
  /**
8
- * Assists in cache management for WP Job Management posts and terms.
9
- *
10
- * @package wp-job-manager
11
- * @since 1.0.0
12
  */
13
  class WP_Job_Manager_Cache_Helper {
14
 
15
- /**
16
- * Initializes cache hooks.
17
- */
18
  public static function init() {
19
  add_action( 'save_post', array( __CLASS__, 'flush_get_job_listings_cache' ) );
20
- add_action( 'delete_post', array( __CLASS__, 'flush_get_job_listings_cache' ) );
21
- add_action( 'trash_post', array( __CLASS__, 'flush_get_job_listings_cache' ) );
22
  add_action( 'job_manager_my_job_do_action', array( __CLASS__, 'job_manager_my_job_do_action' ) );
23
  add_action( 'set_object_terms', array( __CLASS__, 'set_term' ), 10, 4 );
24
  add_action( 'edited_term', array( __CLASS__, 'edited_term' ), 10, 3 );
25
  add_action( 'create_term', array( __CLASS__, 'edited_term' ), 10, 3 );
26
  add_action( 'delete_term', array( __CLASS__, 'edited_term' ), 10, 3 );
27
  add_action( 'job_manager_clear_expired_transients', array( __CLASS__, 'clear_expired_transients' ), 10 );
28
- add_action( 'transition_post_status', array( __CLASS__, 'maybe_clear_count_transients' ), 10, 3 );
29
  }
30
 
31
  /**
32
- * Flushes the cache.
33
- *
34
- * @param int|WP_Post $post_id
35
  */
36
  public static function flush_get_job_listings_cache( $post_id ) {
37
  if ( 'job_listing' === get_post_type( $post_id ) ) {
@@ -40,9 +29,7 @@ class WP_Job_Manager_Cache_Helper {
40
  }
41
 
42
  /**
43
- * Refreshes the Job Listing cache when performing actions on it.
44
- *
45
- * @param string $action
46
  */
47
  public static function job_manager_my_job_do_action( $action ) {
48
  if ( 'mark_filled' === $action || 'mark_not_filled' === $action ) {
@@ -51,30 +38,21 @@ class WP_Job_Manager_Cache_Helper {
51
  }
52
 
53
  /**
54
- * Refreshes the Job Listing cache when terms are updated.
55
- *
56
- * @param string|int $object_id
57
- * @param string $terms
58
- * @param string $tt_ids
59
- * @param string $taxonomy
60
  */
61
  public static function set_term( $object_id = '', $terms = '', $tt_ids = '', $taxonomy = '' ) {
62
  self::get_transient_version( 'jm_get_' . sanitize_text_field( $taxonomy ), true );
63
  }
64
 
65
  /**
66
- * Refreshes the Job Listing cache when terms are updated.
67
- *
68
- * @param string|int $term_id
69
- * @param string|int $tt_id
70
- * @param string $taxonomy
71
  */
72
  public static function edited_term( $term_id = '', $tt_id = '', $taxonomy = '' ) {
73
  self::get_transient_version( 'jm_get_' . sanitize_text_field( $taxonomy ), true );
74
  }
75
 
76
  /**
77
- * Gets transient version.
78
  *
79
  * When using transients with unpredictable names, e.g. those containing an md5
80
  * hash in the name, we need a way to invalidate them all at once.
@@ -86,9 +64,9 @@ class WP_Job_Manager_Cache_Helper {
86
  * to append a unique string (based on time()) to each transient. When transients
87
  * are invalidated, the transient version will increment and data will be regenerated.
88
  *
89
- * @param string $group Name for the group of transients we need to invalidate.
90
- * @param boolean $refresh True to force a new version (Default: false).
91
- * @return string Transient version based on time(), 10 digits.
92
  */
93
  public static function get_transient_version( $group, $refresh = false ) {
94
  $transient_name = $group . '-transient-version';
@@ -105,142 +83,30 @@ class WP_Job_Manager_Cache_Helper {
105
  * When the transient version increases, this is used to remove all past transients to avoid filling the DB.
106
  *
107
  * Note; this only works on transients appended with the transient version, and when object caching is not being used.
108
- *
109
- * @param string $version
110
  */
111
  private static function delete_version_transients( $version ) {
112
  if ( ! wp_using_ext_object_cache() && ! empty( $version ) ) {
113
  global $wpdb;
114
- $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s;", '\_transient\_%' . $version ) );
115
  }
116
  }
117
 
118
- /**
119
- * Clear expired transients.
120
  */
121
  public static function clear_expired_transients() {
122
  global $wpdb;
123
 
124
  if ( ! wp_using_ext_object_cache() && ! defined( 'WP_SETUP_CONFIG' ) && ! defined( 'WP_INSTALLING' ) ) {
125
- $wpdb->query( $wpdb->prepare( "
126
  DELETE a, b FROM $wpdb->options a, $wpdb->options b
127
  WHERE a.option_name LIKE %s
128
  AND a.option_name NOT LIKE %s
129
  AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
130
- AND b.option_value < %s;",
131
- $wpdb->esc_like( '_transient_jm_' ) . '%',
132
- $wpdb->esc_like( '_transient_timeout_jm_' ) . '%',
133
- time()
134
- ) );
135
  }
136
  }
137
-
138
- /**
139
- * Maybe remove pending count transients
140
- *
141
- * When a supported post type status is updated, check if any cached count transients
142
- * need to be removed, and remove the
143
- *
144
- * @since 1.27.0
145
- *
146
- * @param string $new_status New post status.
147
- * @param string $old_status Old post status.
148
- * @param WP_Post $post Post object.
149
- */
150
- public static function maybe_clear_count_transients( $new_status, $old_status, $post ) {
151
- global $wpdb;
152
-
153
- /**
154
- * Get supported post types for count caching
155
- *
156
- * @since 1.27.0
157
- *
158
- * @param array $post_types Post types that should be cached.
159
- * @param string $new_status New post status.
160
- * @param string $old_status Old post status.
161
- * @param WP_Post $post Post object.
162
- */
163
- $post_types = apply_filters( 'wpjm_count_cache_supported_post_types', array( 'job_listing' ), $new_status, $old_status, $post );
164
-
165
- // Only proceed when statuses do not match, and post type is supported post type.
166
- if ( $new_status === $old_status || ! in_array( $post->post_type, $post_types, true ) ) {
167
- return;
168
- }
169
-
170
- /**
171
- * Get supported post statuses for count caching
172
- *
173
- * @since 1.27.0
174
- *
175
- * @param array $post_statuses Post statuses that should be cached.
176
- * @param string $new_status New post status.
177
- * @param string $old_status Old post status.
178
- * @param WP_Post $post Post object.
179
- */
180
- $valid_statuses = apply_filters( 'wpjm_count_cache_supported_statuses', array( 'pending' ), $new_status, $old_status, $post );
181
-
182
- $rlike = array();
183
- // New status transient option name.
184
- if ( in_array( $new_status, $valid_statuses, true ) ) {
185
- $rlike[] = "^_transient_jm_{$new_status}_{$post->post_type}_count_user_";
186
- }
187
- // Old status transient option name.
188
- if ( in_array( $old_status, $valid_statuses, true ) ) {
189
- $rlike[] = "^_transient_jm_{$old_status}_{$post->post_type}_count_user_";
190
- }
191
-
192
- if ( empty( $rlike ) ) {
193
- return;
194
- }
195
-
196
- $transients = $wpdb->get_col( $wpdb->prepare(
197
- "SELECT option_name FROM $wpdb->options WHERE option_name RLIKE %s",
198
- implode( '|', $rlike )
199
- ) );
200
-
201
- // For each transient...
202
- foreach ( $transients as $transient ) {
203
- // Strip away the WordPress prefix in order to arrive at the transient key.
204
- $key = str_replace( '_transient_', '', $transient );
205
- // Now that we have the key, use WordPress core to the delete the transient.
206
- delete_transient( $key );
207
- }
208
-
209
- // Sometimes transients are not in the DB, so we have to do this too:.
210
- wp_cache_flush();
211
- }
212
-
213
- /**
214
- * Get Listings Count from Cache
215
- *
216
- * @since 1.27.0
217
- *
218
- * @param string $post_type
219
- * @param string $status
220
- * @param bool $force Force update cache.
221
- *
222
- * @return int
223
- */
224
- public static function get_listings_count( $post_type = 'job_listing', $status = 'pending', $force = false ) {
225
-
226
- // Get user based cache transient.
227
- $user_id = get_current_user_id();
228
- $transient = "jm_{$status}_{$post_type}_count_user_{$user_id}";
229
-
230
- // Set listings_count value from cache if exists, otherwise set to 0 as default.
231
- $cached_count = get_transient( $transient );
232
- $status_count = $cached_count ? $cached_count : 0;
233
-
234
- // $cached_count will be false if transient does not exist.
235
- if ( false === $cached_count || $force ) {
236
- $count_posts = wp_count_posts( $post_type, 'readable' );
237
- // Default to 0 $status if object does not have a value.
238
- $status_count = isset( $count_posts->$status ) ? $count_posts->$status : 0;
239
- set_transient( $transient, $status_count, DAY_IN_SECONDS * 7 );
240
- }
241
-
242
- return $status_count;
243
- }
244
  }
245
 
246
  WP_Job_Manager_Cache_Helper::init();
5
  }
6
 
7
  /**
8
+ * WP_Job_Manager_Cache_Helper class.
 
 
 
9
  */
10
  class WP_Job_Manager_Cache_Helper {
11
 
 
 
 
12
  public static function init() {
13
  add_action( 'save_post', array( __CLASS__, 'flush_get_job_listings_cache' ) );
 
 
14
  add_action( 'job_manager_my_job_do_action', array( __CLASS__, 'job_manager_my_job_do_action' ) );
15
  add_action( 'set_object_terms', array( __CLASS__, 'set_term' ), 10, 4 );
16
  add_action( 'edited_term', array( __CLASS__, 'edited_term' ), 10, 3 );
17
  add_action( 'create_term', array( __CLASS__, 'edited_term' ), 10, 3 );
18
  add_action( 'delete_term', array( __CLASS__, 'edited_term' ), 10, 3 );
19
  add_action( 'job_manager_clear_expired_transients', array( __CLASS__, 'clear_expired_transients' ), 10 );
 
20
  }
21
 
22
  /**
23
+ * Flush the cache
 
 
24
  */
25
  public static function flush_get_job_listings_cache( $post_id ) {
26
  if ( 'job_listing' === get_post_type( $post_id ) ) {
29
  }
30
 
31
  /**
32
+ * Flush the cache
 
 
33
  */
34
  public static function job_manager_my_job_do_action( $action ) {
35
  if ( 'mark_filled' === $action || 'mark_not_filled' === $action ) {
38
  }
39
 
40
  /**
41
+ * When any post has a term set
 
 
 
 
 
42
  */
43
  public static function set_term( $object_id = '', $terms = '', $tt_ids = '', $taxonomy = '' ) {
44
  self::get_transient_version( 'jm_get_' . sanitize_text_field( $taxonomy ), true );
45
  }
46
 
47
  /**
48
+ * When any term is edited
 
 
 
 
49
  */
50
  public static function edited_term( $term_id = '', $tt_id = '', $taxonomy = '' ) {
51
  self::get_transient_version( 'jm_get_' . sanitize_text_field( $taxonomy ), true );
52
  }
53
 
54
  /**
55
+ * Get transient version
56
  *
57
  * When using transients with unpredictable names, e.g. those containing an md5
58
  * hash in the name, we need a way to invalidate them all at once.
64
  * to append a unique string (based on time()) to each transient. When transients
65
  * are invalidated, the transient version will increment and data will be regenerated.
66
  *
67
+ * @param string $group Name for the group of transients we need to invalidate
68
+ * @param boolean $refresh true to force a new version
69
+ * @return string transient version based on time(), 10 digits
70
  */
71
  public static function get_transient_version( $group, $refresh = false ) {
72
  $transient_name = $group . '-transient-version';
83
  * When the transient version increases, this is used to remove all past transients to avoid filling the DB.
84
  *
85
  * Note; this only works on transients appended with the transient version, and when object caching is not being used.
 
 
86
  */
87
  private static function delete_version_transients( $version ) {
88
  if ( ! wp_using_ext_object_cache() && ! empty( $version ) ) {
89
  global $wpdb;
90
+ $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s;", "\_transient\_%" . $version ) );
91
  }
92
  }
93
 
94
+ /**
95
+ * Clear expired transients
96
  */
97
  public static function clear_expired_transients() {
98
  global $wpdb;
99
 
100
  if ( ! wp_using_ext_object_cache() && ! defined( 'WP_SETUP_CONFIG' ) && ! defined( 'WP_INSTALLING' ) ) {
101
+ $sql = "
102
  DELETE a, b FROM $wpdb->options a, $wpdb->options b
103
  WHERE a.option_name LIKE %s
104
  AND a.option_name NOT LIKE %s
105
  AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
106
+ AND b.option_value < %s;";
107
+ $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_transient_jm_' ) . '%', $wpdb->esc_like( '_transient_timeout_jm_' ) . '%', time() ) );
 
 
 
108
  }
109
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  }
111
 
112
  WP_Job_Manager_Cache_Helper::init();
includes/class-wp-job-manager-category-walker.php CHANGED
@@ -1,77 +1,49 @@
1
  <?php
2
- if ( ! defined( 'ABSPATH' ) ) {
3
- exit; // Exit if accessed directly.
4
- }
5
 
6
  /**
7
- * Walks through categories.
8
  *
9
  * @extends Walker
10
- * @package wp-job-manager
11
- * @since 1.0.0
12
  */
13
  class WP_Job_Manager_Category_Walker extends Walker {
14
 
15
- /**
16
- * Tree type that the class handles.
17
- *
18
- * @var string
19
- */
20
- public $tree_type = 'category';
21
-
22
- /**
23
- * Database fields to use.
24
- *
25
- * @var array
26
- */
27
- public $db_fields = array(
28
- 'parent' => 'parent',
29
- 'id' => 'term_id',
30
- 'slug' => 'slug',
31
- );
32
 
33
  /**
34
- * Start the list walker.
35
- *
36
  * @see Walker::start_el()
37
  * @since 2.1.0
38
  *
39
  * @param string $output Passed by reference. Used to append additional content.
40
- * @param object $object Category data object.
41
- * @param int $depth Depth of category in reference to parents.
42
- * @param array $args
43
- * @param int $current_object_id
44
  */
45
- public function start_el( &$output, $object, $depth = 0, $args = array(), $current_object_id = 0 ) {
46
 
47
- if ( ! empty( $args['hierarchical'] ) ) {
48
- $pad = str_repeat( '&nbsp;', $depth * 3 );
49
- } else {
50
  $pad = '';
51
- }
52
 
53
  $cat_name = apply_filters( 'list_product_cats', $object->name, $object );
54
 
55
- $value = isset( $args['value'] ) && 'id' === $args['value'] ? $object->term_id : $object->slug;
56
 
57
- $output .= "\t<option class=\"level-" . intval( $depth ) . '" value="' . esc_attr( $value ) . '"';
58
 
59
- if ( isset( $args['selected'] ) && (
60
- $value == $args['selected'] // phpcs:ignore WordPress.PHP.StrictComparisons
61
- || ( is_array( $args['selected'] ) && in_array( $value, $args['selected'] ) ) // phpcs:ignore WordPress.PHP.StrictInArray
62
- )
63
- ) {
64
  $output .= ' selected="selected"';
65
- }
66
 
67
  $output .= '>';
68
 
69
- $output .= $pad . esc_html( $cat_name );
70
 
71
  if ( ! empty( $args['show_count'] ) ) {
72
- $output .= '&nbsp;(' . intval( $object->count ) . ')';
73
  }
74
 
75
  $output .= "</option>\n";
76
  }
77
- }
1
  <?php
2
+ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
3
 
4
  /**
5
+ * WP_Job_Manager_Category_Walker class.
6
  *
7
  * @extends Walker
 
 
8
  */
9
  class WP_Job_Manager_Category_Walker extends Walker {
10
 
11
+ var $tree_type = 'category';
12
+ var $db_fields = array ('parent' => 'parent', 'id' => 'term_id', 'slug' => 'slug' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
  /**
 
 
15
  * @see Walker::start_el()
16
  * @since 2.1.0
17
  *
18
  * @param string $output Passed by reference. Used to append additional content.
19
+ * @param object $category Category data object.
20
+ * @param int $depth Depth of category in reference to parents.
21
+ * @param array $args
 
22
  */
23
+ function start_el( &$output, $object, $depth = 0, $args = array(), $current_object_id = 0 ) {
24
 
25
+ if ( ! empty( $args['hierarchical'] ) )
26
+ $pad = str_repeat('&nbsp;', $depth * 3);
27
+ else
28
  $pad = '';
 
29
 
30
  $cat_name = apply_filters( 'list_product_cats', $object->name, $object );
31
 
32
+ $value = isset( $args['value'] ) && $args['value'] == 'id' ? $object->term_id : $object->slug;
33
 
34
+ $output .= "\t<option class=\"level-$depth\" value=\"" . $value . "\"";
35
 
36
+ if ( $value == $args['selected'] || ( is_array( $args['selected'] ) && in_array( $value, $args['selected'] ) ) )
 
 
 
 
37
  $output .= ' selected="selected"';
 
38
 
39
  $output .= '>';
40
 
41
+ $output .= $pad . $cat_name;
42
 
43
  if ( ! empty( $args['show_count'] ) ) {
44
+ $output .= '&nbsp;(' . $object->count . ')';
45
  }
46
 
47
  $output .= "</option>\n";
48
  }
49
+ }
includes/class-wp-job-manager-data-cleaner.php DELETED
@@ -1,367 +0,0 @@
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(
195
- array(
196
- 'post_type' => $post_type,
197
- 'post_status' => 'any',
198
- 'numberposts' => -1,
199
- 'fields' => 'ids',
200
- )
201
- );
202
-
203
- foreach ( $items as $item ) {
204
- wp_trash_post( $item );
205
- }
206
- }
207
- }
208
-
209
- /**
210
- * Cleanup data for taxonomies.
211
- *
212
- * @access private
213
- */
214
- private static function cleanup_taxonomies() {
215
- global $wpdb;
216
-
217
- foreach ( self::$taxonomies as $taxonomy ) {
218
- $terms = $wpdb->get_results(
219
- $wpdb->prepare(
220
- "SELECT term_id, term_taxonomy_id FROM $wpdb->term_taxonomy WHERE taxonomy = %s",
221
- $taxonomy
222
- )
223
- );
224
-
225
- // Delete all data for each term.
226
- foreach ( $terms as $term ) {
227
- $wpdb->delete( $wpdb->term_relationships, array( 'term_taxonomy_id' => $term->term_taxonomy_id ) );
228
- $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $term->term_taxonomy_id ) );
229
- $wpdb->delete( $wpdb->terms, array( 'term_id' => $term->term_id ) );
230
- $wpdb->delete( $wpdb->termmeta, array( 'term_id' => $term->term_id ) );
231
- }
232
-
233
- if ( function_exists( 'clean_taxonomy_cache' ) ) {
234
- clean_taxonomy_cache( $taxonomy );
235
- }
236
- }
237
- }
238
-
239
- /**
240
- * Cleanup data for pages.
241
- *
242
- * @access private
243
- */
244
- private static function cleanup_pages() {
245
- // Trash the Submit Job page.
246
- $submit_job_form_page_id = get_option( 'job_manager_submit_job_form_page_id' );
247
- if ( $submit_job_form_page_id ) {
248
- wp_trash_post( $submit_job_form_page_id );
249
- }
250
-
251
- // Trash the Job Dashboard page.
252
- $job_dashboard_page_id = get_option( 'job_manager_job_dashboard_page_id' );
253
- if ( $job_dashboard_page_id ) {
254
- wp_trash_post( $job_dashboard_page_id );
255
- }
256
-
257
- // Trash the Jobs page.
258
- $jobs_page_id = get_option( 'job_manager_jobs_page_id' );
259
- if ( $jobs_page_id ) {
260
- wp_trash_post( $jobs_page_id );
261
- }
262
- }
263
-
264
- /**
265
- * Cleanup data for options.
266
- *
267
- * @access private
268
- */
269
- private static function cleanup_options() {
270
- foreach ( self::$options as $option ) {
271
- delete_option( $option );
272
- }
273
- }
274
-
275
- /**
276
- * Cleanup data for site options.
277
- *
278
- * @access private
279
- */
280
- private static function cleanup_site_options() {
281
- foreach ( self::$site_options as $option ) {
282
- delete_site_option( $option );
283
- }
284
- }
285
-
286
- /**
287
- * Cleanup transients from the database.
288
- *
289
- * @access private
290
- */
291
- private static function cleanup_transients() {
292
- global $wpdb;
293
-
294
- foreach ( array( '_transient_', '_transient_timeout_' ) as $prefix ) {
295
- foreach ( self::$transients as $transient ) {
296
- $wpdb->query(
297
- $wpdb->prepare(
298
- "DELETE FROM $wpdb->options WHERE option_name RLIKE %s",
299
- $prefix . $transient
300
- )
301
- );
302
- }
303
- }
304
- }
305
-
306
- /**
307
- * Cleanup data for roles and caps.
308
- *
309
- * @access private
310
- */
311
- private static function cleanup_roles_and_caps() {
312
- global $wp_roles;
313
-
314
- // Remove caps from roles.
315
- $role_names = array_keys( $wp_roles->roles );
316
- foreach ( $role_names as $role_name ) {
317
- $role = get_role( $role_name );
318
- self::remove_all_job_manager_caps( $role );
319
- }
320
-
321
- // Remove caps and role from users.
322
- $users = get_users( array() );
323
- foreach ( $users as $user ) {
324
- self::remove_all_job_manager_caps( $user );
325
- $user->remove_role( self::$role );
326
- }
327
-
328
- // Remove role.
329
- remove_role( self::$role );
330
- }
331
-
332
- /**
333
- * Helper method to remove WPJM caps from a user or role object.
334
- *
335
- * @param (WP_User|WP_Role) $object the user or role object.
336
- */
337
- private static function remove_all_job_manager_caps( $object ) {
338
- foreach ( self::$caps as $cap ) {
339
- $object->remove_cap( $cap );
340
- }
341
- }
342
-
343
- /**
344
- * Cleanup user meta from the database.
345
- *
346
- * @access private
347
- */
348
- private static function cleanup_user_meta() {
349
- global $wpdb;
350
-
351
- foreach ( self::$user_meta_keys as $meta_key ) {
352
- $wpdb->delete( $wpdb->usermeta, array( 'meta_key' => $meta_key ) );
353
- }
354
- }
355
-
356
- /**
357
- * Cleanup cron jobs. Note that this should be done on deactivation, but
358
- * doing it here as well for safety.
359
- *
360
- * @access private
361
- */
362
- private static function cleanup_cron_jobs() {
363
- foreach ( self::$cron_jobs as $job ) {
364
- wp_clear_scheduled_hook( $job );
365
- }
366
- }
367
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/class-wp-job-manager-data-exporter.php DELETED
@@ -1,89 +0,0 @@
1
- <?php
2
- /**
3
- * Defines a class to handle the user data export
4
- *
5
- * @package wp-job-manager
6
- * @since 1.31.1
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit; // Exit if accessed directly.
11
- }
12
-
13
- /**
14
- * Handles the user data export.
15
- *
16
- * @package
17
- * @since
18
- */
19
- class WP_Job_Manager_Data_Exporter {
20
- /**
21
- * Register the user data exporter method
22
- *
23
- * @param array $exporters The exporter array.
24
- * @return array $exporters The exporter array.
25
- */
26
- public static function register_wpjm_user_data_exporter( $exporters ) {
27
- $exporters['wp-job-manager'] = array(
28
- 'exporter_friendly_name' => __( 'WP Job Manager', 'wp-job-manager' ),
29
- 'callback' => array( __CLASS__, 'user_data_exporter' ),
30
- );
31
- return $exporters;
32
- }
33
-
34
- /**
35
- * Data exporter
36
- *
37
- * @param string $email_address User email address.
38
- * @return array
39
- */
40
- public static function user_data_exporter( $email_address ) {
41
- $user = get_user_by( 'email', $email_address );
42
- if ( false === $user ) {
43
- return;
44
- }
45
-
46
- $export_items = array();
47
- $user_data_to_export = array();
48
- $user_meta_keys = array(
49
- '_company_logo' => __( 'Company Logo', 'wp-job-manager' ),
50
- '_company_name' => __( 'Company Name', 'wp-job-manager' ),
51
- '_company_website' => __( 'Company Website', 'wp-job-manager' ),
52
- '_company_tagline' => __( 'Company Tagline', 'wp-job-manager' ),
53
- '_company_twitter' => __( 'Company Twitter', 'wp-job-manager' ),
54
- '_company_video' => __( 'Company Video', 'wp-job-manager' ),
55
- );
56
-
57
- foreach ( $user_meta_keys as $user_meta_key => $name ) {
58
- $user_meta = get_user_meta( $user->ID, $user_meta_key, true );
59
-
60
- if ( empty( $user_meta ) ) {
61
- continue;
62
- }
63
-
64
- if ( '_company_logo' === $user_meta_key ) {
65
- $user_meta = wp_get_attachment_url( $user_meta );
66
- if ( false === $user_meta ) {
67
- continue;
68
- }
69
- }
70
-
71
- $user_data_to_export[] = array(
72
- 'name' => $name,
73
- 'value' => $user_meta,
74
- );
75
- }
76
-
77
- $export_items[] = array(
78
- 'group_id' => 'wpjm-user-data',
79
- 'group_label' => __( 'WP Job Manager User Data', 'wp-job-manager' ),
80
- 'item_id' => "wpjm-user-data-{$user->ID}",
81
- 'data' => $user_data_to_export,
82
- );
83
-
84
- return array(
85
- 'data' => $export_items,
86
- 'done' => true,
87
- );
88
- }
89
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/class-wp-job-manager-email-notifications.php DELETED
@@ -1,827 +0,0 @@
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
- * Notifications to be scheduled.
20
- *
21
- * @var array
22
- */
23
- private static $deferred_notifications = array();
24
-
25
- /**
26
- * Sets up initial hooks.
27
- *
28
- * @static
29
- */
30
- public static function init() {
31
- add_action( 'job_manager_send_notification', array( __CLASS__, 'schedule_notification' ), 10, 2 );
32
- add_action( 'job_manager_email_init', array( __CLASS__, 'lazy_init' ) );
33
- add_action( 'job_manager_email_job_details', array( __CLASS__, 'output_job_details' ), 10, 4 );
34
- add_action( 'job_manager_email_header', array( __CLASS__, 'output_header' ), 10, 3 );
35
- add_action( 'job_manager_email_footer', array( __CLASS__, 'output_footer' ), 10, 3 );
36
- add_action( 'job_manager_email_daily_notices', array( __CLASS__, 'send_employer_expiring_notice' ) );
37
- add_action( 'job_manager_email_daily_notices', array( __CLASS__, 'send_admin_expiring_notice' ) );
38
- add_filter( 'job_manager_settings', array( __CLASS__, 'add_job_manager_email_settings' ), 1 );
39
- add_action( 'job_manager_job_submitted', array( __CLASS__, 'send_new_job_notification' ) );
40
- add_action( 'job_manager_user_edit_job_listing', array( __CLASS__, 'send_updated_job_notification' ) );
41
- }
42
-
43
- /**
44
- * Gets list of email notifications handled by WP Job Manager core.
45
- *
46
- * @return array
47
- */
48
- public static function core_email_notifications() {
49
- return array(
50
- 'WP_Job_Manager_Email_Admin_New_Job',
51
- 'WP_Job_Manager_Email_Admin_Updated_Job',
52
- 'WP_Job_Manager_Email_Admin_Expiring_Job',
53
- 'WP_Job_Manager_Email_Employer_Expiring_Job',
54
- );
55
- }
56
-
57
- /**
58
- * Sets up an email notification to be sent at the end of the script's execution.
59
- *
60
- * Do not call manually.
61
- *
62
- * @access private
63
- *
64
- * @param string $notification
65
- * @param array $args
66
- */
67
- public static function schedule_notification( $notification, $args = array() ) {
68
- self::maybe_init();
69
-
70
- self::$deferred_notifications[] = array( $notification, $args );
71
- }
72
-
73
- /**
74
- * Sends all notifications collected during execution.
75
- *
76
- * Do not call manually.
77
- *
78
- * @access private
79
- */
80
- public static function send_deferred_notifications() {
81
- $email_notifications = self::get_email_notifications( true );
82
- foreach ( self::$deferred_notifications as $email ) {
83
- if (
84
- ! is_string( $email[0] )
85
- || ! isset( $email_notifications[ $email[0] ] )
86
- ) {
87
- continue;
88
- }
89
-
90
- $email_class = $email_notifications[ $email[0] ];
91
- $email_notification_key = $email[0];
92
- $email_args = is_array( $email[1] ) ? $email[1] : array();
93
-
94
- self::send_email( $email[0], new $email_class( $email_args, self::get_email_settings( $email_notification_key ) ) );
95
- }
96
- }
97
-
98
- /**
99
- * Initialize if necessary.
100
- */
101
- public static function maybe_init() {
102
- if ( 0 === did_action( 'job_manager_email_init' ) ) {
103
- /**
104
- * Lazily load remaining files needed for email notifications. Do this here instead of in
105
- * `shutdown` for proper logging in case of syntax errors.
106
- *
107
- * @since 1.31.0
108
- */
109
- do_action( 'job_manager_email_init' );
110
- }
111
- }
112
-
113
- /**
114
- * Include email files.
115
- *
116
- * Do not call manually.
117
- *
118
- * @access private
119
- */
120
- public static function lazy_init() {
121
- add_action( 'shutdown', array( __CLASS__, 'send_deferred_notifications' ) );
122
-
123
- include_once JOB_MANAGER_PLUGIN_DIR . '/includes/emails/class-wp-job-manager-email-admin-new-job.php';
124
- include_once JOB_MANAGER_PLUGIN_DIR . '/includes/emails/class-wp-job-manager-email-admin-updated-job.php';
125
- include_once JOB_MANAGER_PLUGIN_DIR . '/includes/emails/class-wp-job-manager-email-employer-expiring-job.php';
126
- include_once JOB_MANAGER_PLUGIN_DIR . '/includes/emails/class-wp-job-manager-email-admin-expiring-job.php';
127
-
128
- if ( ! class_exists( 'Emogrifier' ) && class_exists( 'DOMDocument' ) ) {
129
- include_once JOB_MANAGER_PLUGIN_DIR . '/lib/emogrifier/class-emogrifier.php';
130
- }
131
- }
132
-
133
- /**
134
- * Clear the deferred notifications email array.
135
- *
136
- * Do not call manually. Only for help with tests.
137
- *
138
- * @access private
139
- */
140
- public static function clear_deferred_notifications() {
141
- if ( ! defined( 'PHPUNIT_WPJM_TESTSUITE' ) || ! PHPUNIT_WPJM_TESTSUITE ) {
142
- die( 'This is just for use while testing' );
143
- }
144
- self::$deferred_notifications = array();
145
- }
146
-
147
- /**
148
- * Gets a list of all email notifications that WP Job Manager handles.
149
- *
150
- * @param bool $enabled_notifications_only
151
- * @return array
152
- */
153
- public static function get_email_notifications( $enabled_notifications_only = false ) {
154
- self::maybe_init();
155
-
156
- /**
157
- * Retrieves all email notifications to be sent.
158
- *
159
- * @since 1.31.0
160
- *
161
- * @param array $email_notifications All the email notifications to be registered.
162
- */
163
- $email_notification_classes = array_unique( apply_filters( 'job_manager_email_notifications', self::core_email_notifications() ) );
164
- $email_notifications = array();
165
-
166
- /**
167
- * Email class in loop.
168
- *
169
- * @var WP_Job_Manager_Email $email_class
170
- */
171
- foreach ( $email_notification_classes as $email_class ) {
172
- // Check to make sure email notification is valid.
173
- if ( ! self::is_email_notification_valid( $email_class ) ) {
174
- continue;
175
- }
176
-
177
- // PHP 5.2: Using `call_user_func()` but `$email_class::get_key()` preferred.
178
- $email_notification_key = call_user_func( array( $email_class, 'get_key' ) );
179
- if (
180
- isset( $email_notifications[ $email_notification_key ] )
181
- || ( $enabled_notifications_only && ! self::is_email_notification_enabled( $email_notification_key ) )
182
- ) {
183
- continue;
184
- }
185
-
186
- $email_notifications[ $email_notification_key ] = $email_class;
187
- }
188
-
189
- return $email_notifications;
190
- }
191
-
192
- /**
193
- * Show details about the job listing.
194
- *
195
- * @param WP_Post $job The job listing to show details for.
196
- * @param WP_Job_Manager_Email $email Email object for the notification.
197
- * @param bool $sent_to_admin True if this is being sent to an administrator.
198
- * @param bool $plain_text True if the email is being sent as plain text.
199
- */
200
- public static function output_job_details( $job, $email, $sent_to_admin, $plain_text = false ) {
201
- $template_segment = self::locate_template_file( 'email-job-details', $plain_text );
202
- if ( ! file_exists( $template_segment ) ) {
203
- return;
204
- }
205
-
206
- $fields = self::get_job_detail_fields( $job, $sent_to_admin, $plain_text );
207
-
208
- include $template_segment;
209
- }
210
-
211
- /**
212
- * Get the job fields to show in email templates.
213
- *
214
- * @param WP_Post $job
215
- * @param bool $sent_to_admin
216
- * @param bool $plain_text
217
- * @return array
218
- */
219
- private static function get_job_detail_fields( WP_Post $job, $sent_to_admin, $plain_text = false ) {
220
- $fields = array();
221
-
222
- $fields['job_title'] = array(
223
- 'label' => __( 'Job title', 'wp-job-manager' ),
224
- 'value' => $job->post_title,
225
- );
226
-
227
- if ( $sent_to_admin || 'publish' === $job->post_status ) {
228
- $fields['job_title']['url'] = get_permalink( $job );
229
- }
230
-
231
- $job_location = get_the_job_location( $job );
232
- if ( ! empty( $job_location ) ) {
233
- $fields['job_location'] = array(
234
- 'label' => __( 'Location', 'wp-job-manager' ),
235
- 'value' => $job_location,
236
- );
237
- }
238
-
239
- if ( get_option( 'job_manager_enable_types' ) && wp_count_terms( 'job_listing_type' ) > 0 ) {
240
- $job_types = wpjm_get_the_job_types( $job );
241
- if ( ! empty( $job_types ) ) {
242
- $fields['job_type'] = array(
243
- 'label' => __( 'Job type', 'wp-job-manager' ),
244
- 'value' => implode( ', ', wp_list_pluck( $job_types, 'name' ) ),
245
- );
246
- }
247
- }
248
-
249
- if ( get_option( 'job_manager_enable_categories' ) && wp_count_terms( 'job_listing_category' ) > 0 ) {
250
- $job_categories = wpjm_get_the_job_categories( $job );
251
- if ( ! empty( $job_categories ) ) {
252
- $fields['job_category'] = array(
253
- 'label' => __( 'Job category', 'wp-job-manager' ),
254
- 'value' => implode( ', ', wp_list_pluck( $job_categories, 'name' ) ),
255
- );
256
- }
257
- }
258
-
259
- $company_name = get_the_company_name( $job );
260
- if ( ! empty( $company_name ) ) {
261
- $fields['company_name'] = array(
262
- 'label' => __( 'Company name', 'wp-job-manager' ),
263
- 'value' => $company_name,
264
- );
265
- }
266
-
267
- $company_website = get_the_company_website( $job );
268
- if ( ! empty( $company_website ) ) {
269
- $fields['company_website'] = array(
270
- 'label' => __( 'Company website', 'wp-job-manager' ),
271
- 'value' => $plain_text ? $company_website : sprintf( '<a href="%1$s">%1$s</a>', esc_url( $company_website, array( 'http', 'https' ) ) ),
272
- );
273
- }
274
-
275
- $job_expires = get_post_meta( $job->ID, '_job_expires', true );
276
- if ( ! empty( $job_expires ) ) {
277
- $job_expires_str = date_i18n( get_option( 'date_format' ), strtotime( $job_expires ) );
278
- $fields['job_expires'] = array(
279
- 'label' => __( 'Listing expires', 'wp-job-manager' ),
280
- 'value' => $job_expires_str,
281
- );
282
- }
283
-
284
- if ( $sent_to_admin ) {
285
- $author = get_user_by( 'ID', $job->post_author );
286
- if ( $author instanceof WP_User ) {
287
- $fields['author'] = array(
288
- 'label' => __( 'Posted by', 'wp-job-manager' ),
289
- 'value' => $author->user_nicename,
290
- 'url' => 'mailto:' . $author->user_email,
291
- );
292
- }
293
- }
294
-
295
- /**
296
- * Modify the fields shown in email notifications in the details summary a job listing.
297
- *
298
- * @since 1.31.0
299
- *
300
- * @param array $fields {
301
- * Array of fields. Each field is keyed with a unique identifier.
302
- * {
303
- * @type string $label Label to show next to field.
304
- * @type string $value Value for field.
305
- * @type string $url URL to provide with the value (optional).
306
- * }
307
- * }
308
- * @param WP_Post $job Job listing.
309
- * @param bool $sent_to_admin True if being sent in an admin notification.
310
- * @param bool $plain_text True if being sent as plain text.
311
- */
312
- return apply_filters( 'job_manager_emails_job_detail_fields', $fields, $job, $sent_to_admin, $plain_text );
313
- }
314
-
315
- /**
316
- * Output email header.
317
- *
318
- * @param string $email_notification_key Email notification key for email being sent.
319
- * @param bool $sent_to_admin True if this is being sent to an administrator.
320
- * @param bool $plain_text True if the email is being sent as plain text.
321
- */
322
- public static function output_header( $email_notification_key, $sent_to_admin, $plain_text = false ) {
323
- $template_segment = self::email_template_path_alternative( $email_notification_key, 'email-header', $plain_text );
324
- if ( false === $template_segment ) {
325
- $template_segment = self::locate_template_file( 'email-header', $plain_text );
326
- }
327
- if ( ! $template_segment || ! file_exists( $template_segment ) ) {
328
- return;
329
- }
330
- include $template_segment;
331
- }
332
-
333
- /**
334
- * Output email footer.
335
- *
336
- * @param string $email_notification_key Email notification key for email being sent.
337
- * @param bool $sent_to_admin True if this is being sent to an administrator.
338
- * @param bool $plain_text True if the email is being sent as plain text.
339
- */
340
- public static function output_footer( $email_notification_key, $sent_to_admin, $plain_text = false ) {
341
- $template_segment = self::email_template_path_alternative( $email_notification_key, 'email-footer', $plain_text );
342
- if ( false === $template_segment ) {
343
- $template_segment = self::locate_template_file( 'email-footer', $plain_text );
344
- }
345
- if ( ! $template_segment || ! file_exists( $template_segment ) ) {
346
- return;
347
- }
348
- include $template_segment;
349
- }
350
-
351
- /**
352
- * Checks for an alternative email template segment in the template path specified by the current email.
353
- * Useful to provide alternative email headers and footers for a specific WPJM extension plugin.
354
- *
355
- * @param string $email_notification_key Email notification key for email being sent.
356
- * @param string $template_name Name of the template to check.
357
- * @param bool $plain_text True if the email is being sent as plain text.
358
- * @return bool|string Returns path to template path alternative or false if none exists.
359
- */
360
- private static function email_template_path_alternative( $email_notification_key, $template_name, $plain_text ) {
361
- $email_class = self::get_email_class( $email_notification_key );
362
- if ( ! $email_class || ! is_subclass_of( $email_class, 'WP_Job_Manager_Email_Template' ) ) {
363
- return false;
364
- }
365
-
366
- $template_default_path = call_user_func( array( $email_class, 'get_template_default_path' ) );
367
- if ( '' === $template_default_path ) {
368
- return false;
369
- }
370
-
371
- $template_path = call_user_func( array( $email_class, 'get_template_path' ) );
372
- $template = self::locate_template_file( $template_name, $plain_text, $template_path, $template_default_path );
373
- if ( '' === $template ) {
374
- return false;
375
- }
376
-
377
- return $template;
378
- }
379
-
380
- /**
381
- * Locate template file.
382
- *
383
- * @param string $template_name
384
- * @param bool $plain_text
385
- * @param string $template_path
386
- * @param string $default_path
387
- * @return string
388
- */
389
- public static function locate_template_file( $template_name, $plain_text = false, $template_path = 'job_manager', $default_path = '' ) {
390
- return locate_job_manager_template( WP_Job_Manager_Email_Template::generate_template_file_name( $template_name, $plain_text ), $template_path, $default_path );
391
- }
392
-
393
- /**
394
- * Add email notification settings for the job manager context.
395
- *
396
- * @param array $settings
397
- * @return array
398
- */
399
- public static function add_job_manager_email_settings( $settings ) {
400
- return self::add_email_settings( $settings, WP_Job_Manager_Email::get_context() );
401
- }
402
-
403
- /**
404
- * Add email notification settings for a context.
405
- *
406
- * @param array $settings
407
- * @param string $context
408
- * @return array
409
- */
410
- public static function add_email_settings( $settings, $context ) {
411
- $email_notifications = self::get_email_notifications( false );
412
- $email_settings = array();
413
-
414
- foreach ( $email_notifications as $email_notification_key => $email_class ) {
415
- $email_notification_context = call_user_func( array( $email_class, 'get_context' ) );
416
- if ( $context !== $email_notification_context ) {
417
- continue;
418
- }
419
-
420
- $email_settings[] = array(
421
- 'type' => 'multi_enable_expand',
422
- 'class' => 'email-setting-row no-separator',
423
- 'name' => self::EMAIL_SETTING_PREFIX . call_user_func( array( $email_class, 'get_key' ) ),
424
- 'enable_field' => array(
425
- 'name' => self::EMAIL_SETTING_ENABLED,
426
- 'cb_label' => call_user_func( array( $email_class, 'get_name' ) ),
427
- 'desc' => call_user_func( array( $email_class, 'get_description' ) ),
428
- ),
429
- 'label' => false,
430
- 'std' => self::get_email_setting_defaults( $email_notification_key ),
431
- 'settings' => self::get_email_setting_fields( $email_notification_key ),
432
- );
433
- }
434
-
435
- if ( ! empty( $email_settings ) ) {
436
- $settings['email_notifications'] = array(
437
- __( 'Email Notifications', 'wp-job-manager' ),
438
- $email_settings,
439
- array(
440
- 'before' => __( 'Select the email notifications to enable.', 'wp-job-manager' ),
441
- ),
442
- );
443
- }
444
-
445
- return $settings;
446
- }
447
-
448
- /**
449
- * Checks if a particular notification is enabled or not.
450
- *
451
- * @param string $email_notification_key
452
- * @return bool
453
- */
454
- public static function is_email_notification_enabled( $email_notification_key ) {
455
- $settings = self::get_email_settings( $email_notification_key );
456
-
457
- $is_email_notification_enabled = ! empty( $settings[ self::EMAIL_SETTING_ENABLED ] );
458
-
459
- /**
460
- * Filter whether an notification email is enabled.
461
- *
462
- * @since 1.31.0
463
- *
464
- * @param bool $is_email_notification_enabled
465
- * @param string $email_notification_key
466
- */
467
- return apply_filters( 'job_manager_email_is_email_notification_enabled', $is_email_notification_enabled, $email_notification_key );
468
- }
469
-
470
- /**
471
- * Checks if we should send emails using plain text.
472
- *
473
- * @param string $email_notification_key
474
- * @return bool
475
- */
476
- public static function send_as_plain_text( $email_notification_key ) {
477
- $settings = self::get_email_settings( $email_notification_key );
478
-
479
- $send_as_plain_text = ! empty( $settings[ self::EMAIL_SETTING_PLAIN_TEXT ] );
480
-
481
- /**
482
- * Filter whether to send emails as plain text.
483
- *
484
- * @since 1.31.0
485
- *
486
- * @param bool $send_as_plain_text
487
- * @param string $email_notification_key
488
- */
489
- return apply_filters( 'job_manager_email_send_as_plain_text', $send_as_plain_text, $email_notification_key );
490
- }
491
-
492
- /**
493
- * Sending notices to employers for expiring job listings.
494
- */
495
- public static function send_employer_expiring_notice() {
496
- self::maybe_init();
497
-
498
- $email_key = WP_Job_Manager_Email_Employer_Expiring_Job::get_key();
499
- if ( ! self::is_email_notification_enabled( $email_key ) ) {
500
- return;
501
- }
502
- $settings = self::get_email_settings( $email_key );
503
- $days_notice = WP_Job_Manager_Email_Employer_Expiring_Job::get_notice_period( $settings );
504
- self::send_expiring_notice( $email_key, $days_notice );
505
- }
506
-
507
- /**
508
- * Sending notices to the site administrator for expiring job listings.
509
- */
510
- public static function send_admin_expiring_notice() {
511
- self::maybe_init();
512
-
513
- $email_key = WP_Job_Manager_Email_Admin_Expiring_Job::get_key();
514
- if ( ! self::is_email_notification_enabled( $email_key ) ) {
515
- return;
516
- }
517
- $settings = self::get_email_settings( $email_key );
518
- $days_notice = WP_Job_Manager_Email_Admin_Expiring_Job::get_notice_period( $settings );
519
- self::send_expiring_notice( $email_key, $days_notice );
520
- }
521
-
522
- /**
523
- * Fire the action to send a new job notification to the admin.
524
- *
525
- * @param int $job_id
526
- */
527
- public static function send_new_job_notification( $job_id ) {
528
- do_action( 'job_manager_send_notification', 'admin_new_job', array( 'job_id' => $job_id ) );
529
- }
530
-
531
- /**
532
- * Fire the action to send a updated job notification to the admin.
533
- *
534
- * @param int $job_id
535
- */
536
- public static function send_updated_job_notification( $job_id ) {
537
- do_action( 'job_manager_send_notification', 'admin_updated_job', array( 'job_id' => $job_id ) );
538
- }
539
-
540
- /**
541
- * Send notice based on job expiration date.
542
- *
543
- * @param string $email_notification_key
544
- * @param int $days_notice
545
- */
546
- private static function send_expiring_notice( $email_notification_key, $days_notice ) {
547
- global $wpdb;
548
-
549
- $notice_before_ts = current_time( 'timestamp' ) + ( DAY_IN_SECONDS * $days_notice );
550
- $job_ids = $wpdb->get_col( $wpdb->prepare(
551
- "
552
- SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
553
- LEFT JOIN {$wpdb->posts} as posts ON postmeta.post_id = posts.ID
554
- WHERE postmeta.meta_key = '_job_expires'
555
- AND postmeta.meta_value = %s
556
- AND posts.post_status = 'publish'
557
- AND posts.post_type = 'job_listing'
558
- ", date( 'Y-m-d', $notice_before_ts )
559
- ) );
560
-
561
- if ( $job_ids ) {
562
- foreach ( $job_ids as $job_id ) {
563
- do_action( 'job_manager_send_notification', $email_notification_key, array( 'job_id' => $job_id ) );
564
- }
565
- }
566
- }
567
-
568
- /**
569
- * Get the setting fields for an email.
570
- *
571
- * @param string $email_notification_key
572
- * @return array
573
- */
574
- private static function get_email_setting_fields( $email_notification_key ) {
575
- $email_class = self::get_email_class( $email_notification_key );
576
- $core_settings = array(
577
- array(
578
- 'name' => 'plain_text',
579
- 'std' => '0',
580
- 'label' => __( 'Format', 'wp-job-manager' ),
581
- 'type' => 'radio',
582
- 'options' => array(
583
- '1' => __( 'Send plain text email', 'wp-job-manager' ),
584
- '0' => __( 'Send rich text email', 'wp-job-manager' ),
585
- ),
586
- ),
587
- );
588
- $email_settings = call_user_func( array( $email_class, 'get_setting_fields' ) );
589
- return array_merge( $core_settings, $email_settings );
590
- }
591
-
592
- /**
593
- * Get the settings for the email.
594
- *
595
- * @param string $email_notification_key
596
- * @return array
597
- */
598
- private static function get_email_settings( $email_notification_key ) {
599
- $option_name = self::EMAIL_SETTING_PREFIX . $email_notification_key;
600
- $option_value = get_option( $option_name );
601
- if ( empty( $option_value ) || ! is_array( $option_value ) ) {
602
- $option_value = array();
603
- }
604
- $default_settings = self::get_email_setting_defaults( $email_notification_key );
605
-
606
- return array_merge( $default_settings, $option_value );
607
- }
608
-
609
- /**
610
- * Gets the default values for the email notification.
611
- *
612
- * @param string $email_notification_key
613
- * @return array
614
- */
615
- private static function get_email_setting_defaults( $email_notification_key ) {
616
- $settings = self::get_email_setting_fields( $email_notification_key );
617
- $email_class = self::get_email_class( $email_notification_key );
618
-
619
- $defaults = array();
620
- $defaults[ self::EMAIL_SETTING_ENABLED ] = call_user_func( array( $email_class, 'is_default_enabled' ) ) ? '1' : '0';
621
-
622
- foreach ( $settings as $setting ) {
623
- $defaults[ $setting['name'] ] = null;
624
- if ( isset( $setting['std'] ) ) {
625
- $defaults[ $setting['name'] ] = $setting['std'];
626
- }
627
- }
628
-
629
- return $defaults;
630
- }
631
-
632
- /**
633
- * Get the email class from the unique key.
634
- *
635
- * @param string $email_notification_key
636
- * @return bool|string
637
- */
638
- private static function get_email_class( $email_notification_key ) {
639
- $email_notifications = self::get_email_notifications( false );
640
-
641
- return isset( $email_notifications[ $email_notification_key ] ) ? $email_notifications[ $email_notification_key ] : false;
642
- }
643
-
644
- /**
645
- * Returns the total number of deferred notifications to be sent.
646
- *
647
- * Do not use. Used just in unit tests.
648
- *
649
- * @access private
650
- *
651
- * @return int
652
- */
653
- public static function get_deferred_notification_count() {
654
- return count( self::$deferred_notifications );
655
- }
656
-
657
- /**
658
- * Confirms an email notification is valid.
659
- *
660
- * @access private
661
- *
662
- * @param string $email_class
663
- * @return bool
664
- */
665
- private static function is_email_notification_valid( $email_class ) {
666
- // PHP 5.2: Using `call_user_func()` but `$email_class::get_key()` preferred.
667
- return is_string( $email_class )
668
- && class_exists( $email_class )
669
- && is_subclass_of( $email_class, 'WP_Job_Manager_Email' )
670
- && false !== call_user_func( array( $email_class, 'get_key' ) )
671
- && false !== call_user_func( array( $email_class, 'get_name' ) );
672
- }
673
-
674
- /**
675
- * Sends an email notification.
676
- *
677
- * @access private
678
- *
679
- * @param string $email_notification_key
680
- * @param WP_Job_Manager_Email $email
681
- * @return bool
682
- */
683
- private static function send_email( $email_notification_key, WP_Job_Manager_Email $email ) {
684
- if ( ! $email->is_valid() ) {
685
- return false;
686
- }
687
-
688
- $fields = array( 'to', 'from', 'subject', 'rich_content', 'plain_content', 'attachments', 'cc', 'headers' );
689
- $args = array();
690
- foreach ( $fields as $field ) {
691
- $method = 'get_' . $field;
692
-
693
- /**
694
- * Filter email values for job manager notifications.
695
- *
696
- * @since 1.31.0
697
- *
698
- * @param mixed $email_field_value Value to be filtered.
699
- * @param WP_Job_Manager_Email $email Email notification object.
700
- */
701
- $args[ $field ] = apply_filters( "job_manager_email_{$email_notification_key}_{$field}", $email->$method(), $email );
702
- }
703
-
704
- $headers = is_array( $args['headers'] ) ? $args['headers'] : array();
705
-
706
- if ( ! empty( $args['from'] ) ) {
707
- $headers[] = 'From: ' . $args['from'];
708
- }
709
-
710
- if ( ! self::send_as_plain_text( $email_notification_key ) ) {
711
- $headers[] = 'Content-Type: text/html';
712
- }
713
-
714
- $content = self::get_email_content( $email_notification_key, $args );
715
-
716
- /**
717
- * Allows for short-circuiting the actual sending of email notifications.
718
- *
719
- * @since 1.31.0
720
- *
721
- * @param bool $do_send_notification True if we should send the notification.
722
- * @param WP_Job_Manager_Email $email Email notification object.
723
- * @param array $args Email arguments for generating email.
724
- * @param string $content Email content.
725
- * @param array $headers Email headers.
726
- * @param
727
- */
728
- if ( ! apply_filters( 'job_manager_email_do_send_notification', true, $email, $args, $content, $headers ) ) {
729
- return false;
730
- }
731
- return wp_mail( $args['to'], $args['subject'], $content, $headers, $args['attachments'] );
732
- }
733
-
734
- /**
735
- * Generates the content for an email.
736
- *
737
- * @access private
738
- *
739
- * @param string $email_notification_key
740
- * @param array $args
741
- * @return string
742
- */
743
- private static function get_email_content( $email_notification_key, $args ) {
744
- $plain_text = self::send_as_plain_text( $email_notification_key );
745
-
746
- ob_start();
747
-
748
- /**
749
- * Output the header for all job manager emails.
750
- *
751
- * @since 1.31.0
752
- *
753
- * @param string $email_notification_key Unique email notification key.
754
- * @param array $args Arguments passed for generating email.
755
- * @param bool $plain_text True if sending plain text email.
756
- */
757
- do_action( 'job_manager_email_header', $email_notification_key, $args, $plain_text );
758
-
759
- if ( $plain_text ) {
760
- echo wp_kses_post( html_entity_decode( wptexturize( $args['plain_content'] ) ) );
761
- } else {
762
- echo wp_kses_post( wpautop( wptexturize( $args['rich_content'] ) ) );
763
- }
764
-
765
- /**
766
- * Output the footer for all job manager emails.
767
- *
768
- * @since 1.31.0
769
- *
770
- * @param string $email_notification_key Unique email notification key.
771
- * @param array $args Arguments passed for generating email.
772
- * @param bool $plain_text True if sending plain text email.
773
- */
774
- do_action( 'job_manager_email_footer', $email_notification_key, $args, $plain_text );
775
-
776
- $content = ob_get_clean();
777
- if ( ! $plain_text ) {
778
- $content = self::inject_styles( $content );
779
- }
780
-
781
- /**
782
- * Filter the content of the email.
783
- *
784
- * @since 1.31.0
785
- *
786
- * @param string $content Email content.
787
- * @param string $email_notification_key Unique email notification key.
788
- * @param array $args Arguments passed for generating email.
789
- * @param bool $plain_text True if sending plain text email.
790
- */
791
- return apply_filters( 'job_manager_email_content', $content, $email_notification_key, $args, $plain_text );
792
- }
793
-
794
- /**
795
- * Inject inline styles into email content.
796
- *
797
- * @param string $content
798
- * @return string
799
- */
800
- private static function inject_styles( $content ) {
801
- if ( class_exists( 'Emogrifier' ) ) {
802
- try {
803
- $emogrifier = new Emogrifier( $content, self::get_styles() );
804
- $content = $emogrifier->emogrify();
805
- } catch ( Exception $e ) {
806
- trigger_error( 'Unable to inject styles into email notification: ' . $e->getMessage() ); // @codingStandardsIgnoreLine
807
- }
808
- }
809
- return $content;
810
- }
811
-
812
- /**
813
- * Gets the CSS styles to be used in email notifications.
814
- *
815
- * @return bool|string
816
- */
817
- private static function get_styles() {
818
- $email_styles_template = self::locate_template_file( 'email-styles' );
819
- if ( ! file_exists( $email_styles_template ) ) {
820
- return false;
821
- }
822
- ob_start();
823
- include $email_styles_template;
824
- return ob_get_clean();
825
- }
826
-
827
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/class-wp-job-manager-forms.php CHANGED
@@ -1,36 +1,11 @@
1
  <?php
2
  /**
3
- * Base class for all WP Job Manager forms.
4
- *
5
- * @package wp-job-manager
6
- * @since 1.0.0
7
  */
8
  class WP_Job_Manager_Forms {
9
 
10
  /**
11
- * The single instance of the class.
12
- *
13
- * @var self
14
- * @since 1.26.0
15
- */
16
- private static $_instance = null;
17
-
18
- /**
19
- * Allows for accessing single instance of class. Class should only be constructed once per call.
20
- *
21
- * @since 1.26.0
22
- * @static
23
- * @return self Main instance.
24
- */
25
- public static function instance() {
26
- if ( is_null( self::$_instance ) ) {
27
- self::$_instance = new self();
28
- }
29
- return self::$_instance;
30
- }
31
-
32
- /**
33
- * Constructor.
34
  */
35
  public function __construct() {
36
  add_action( 'init', array( $this, 'load_posted_form' ) );
@@ -49,16 +24,16 @@ class WP_Job_Manager_Forms {
49
  * Load a form's class
50
  *
51
  * @param string $form_name
52
- * @return string class name on success, false on failure.
53
  */
54
  private function load_form_class( $form_name ) {
55
  if ( ! class_exists( 'WP_Job_Manager_Form' ) ) {
56
- include 'abstracts/abstract-wp-job-manager-form.php';
57
  }
58
 
59
- // Now try to load the form_name.
60
- $form_class = 'WP_Job_Manager_Form_' . str_replace( '-', '_', $form_name );
61
- $form_file = JOB_MANAGER_PLUGIN_DIR . '/includes/forms/class-wp-job-manager-form-' . $form_name . '.php';
62
 
63
  if ( class_exists( $form_class ) ) {
64
  return call_user_func( array( $form_class, 'instance' ) );
@@ -72,23 +47,22 @@ class WP_Job_Manager_Forms {
72
  include $form_file;
73
  }
74
 
75
- // Init the form.
76
  return call_user_func( array( $form_class, 'instance' ) );
77
  }
78
 
79
  /**
80
- * Returns the form content.
81
  *
82
  * @param string $form_name
83
- * @param array $atts Optional passed attributes.
84
- * @return string|null
85
  */
86
  public function get_form( $form_name, $atts = array() ) {
87
- $form = $this->load_form_class( $form_name );
88
- if ( $form ) {
89
  ob_start();
90
  $form->output( $atts );
91
  return ob_get_clean();
92
  }
93
  }
94
- }
1
  <?php
2
  /**
3
+ * WP_Job_Manager_Forms class.
 
 
 
4
  */
5
  class WP_Job_Manager_Forms {
6
 
7
  /**
8
+ * Constructor
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  */
10
  public function __construct() {
11
  add_action( 'init', array( $this, 'load_posted_form' ) );
24
  * Load a form's class
25
  *
26
  * @param string $form_name
27
+ * @return string class name on success, false on failure
28
  */
29
  private function load_form_class( $form_name ) {
30
  if ( ! class_exists( 'WP_Job_Manager_Form' ) ) {
31
+ include( 'abstracts/abstract-wp-job-manager-form.php' );
32
  }
33
 
34
+ // Now try to load the form_name
35
+ $form_class = 'WP_Job_Manager_Form_' . str_replace( '-', '_', $form_name );
36
+ $form_file = JOB_MANAGER_PLUGIN_DIR . '/includes/forms/class-wp-job-manager-form-' . $form_name . '.php';
37
 
38
  if ( class_exists( $form_class ) ) {
39
  return call_user_func( array( $form_class, 'instance' ) );
47
  include $form_file;
48
  }
49
 
50
+ // Init the form
51
  return call_user_func( array( $form_class, 'instance' ) );
52
  }
53
 
54
  /**
55
+ * get_form function.
56
  *
57
  * @param string $form_name
58
+ * @param array $atts Optional passed attributes
59
+ * @return string
60
  */
61
  public function get_form( $form_name, $atts = array() ) {
62
+ if ( $form = $this->load_form_class( $form_name ) ) {
 
63
  ob_start();
64
  $form->output( $atts );
65
  return ob_get_clean();
66
  }
67
  }
68
+ }
includes/class-wp-job-manager-geocode.php CHANGED
@@ -1,56 +1,24 @@
1
  <?php
2
 
3
- if ( ! defined( 'ABSPATH' ) ) {
4
- exit; // Exit if accessed directly.
5
- }
6
 
7
  /**
8
- * Obtains Geolocation data for posted jobs from Google.
9
  *
10
- * @package wp-job-manager
11
- * @since 1.6.1
12
  */
13
  class WP_Job_Manager_Geocode {
14
 
15
- const GOOGLE_MAPS_GEOCODE_API_URL = 'https://maps.googleapis.com/maps/api/geocode/json';
16
-
17
- /**
18
- * The single instance of the class.
19
- *
20
- * @var self
21
- * @since 1.26.0
22
- */
23
- private static $_instance = null;
24
-
25
- /**
26
- * Allows for accessing single instance of class. Class should only be constructed once per call.
27
- *
28
- * @since 1.26.0
29
- * @static
30
- * @return self Main instance.
31
- */
32
- public static function instance() {
33
- if ( is_null( self::$_instance ) ) {
34
- self::$_instance = new self();
35
- }
36
- return self::$_instance;
37
- }
38
-
39
  /**
40
- * Constructor.
41
  */
42
  public function __construct() {
43
- add_filter( 'job_manager_geolocation_endpoint', array( $this, 'add_geolocation_endpoint_query_args' ), 0, 2 );
44
- add_filter( 'job_manager_geolocation_api_key', array( $this, 'get_google_maps_api_key' ), 0 );
45
  add_action( 'job_manager_update_job_data', array( $this, 'update_location_data' ), 20, 2 );
46
  add_action( 'job_manager_job_location_edited', array( $this, 'change_location_data' ), 20, 2 );
47
  }
48
 
49
  /**
50
- * Updates location data when submitting a job.
51
- *
52
- * @param int $job_id
53
- * @param array $values
54
  */
55
  public function update_location_data( $job_id, $values ) {
56
  if ( apply_filters( 'job_manager_geolocation_enabled', true ) && isset( $values['job']['job_location'] ) ) {
@@ -60,9 +28,8 @@ class WP_Job_Manager_Geocode {
60
  }
61
 
62
  /**
63
- * Changes a jobs location data upon editing.
64
- *
65
- * @param int $job_id
66
  * @param string $new_location
67
  */
68
  public function change_location_data( $job_id, $new_location ) {
@@ -74,19 +41,17 @@ class WP_Job_Manager_Geocode {
74
  }
75
 
76
  /**
77
- * Checks if a job has location data or not.
78
- *
79
- * @param int $job_id
80
  * @return boolean
81
  */
82
  public static function has_location_data( $job_id ) {
83
- return 1 === intval( get_post_meta( $job_id, 'geolocated', true ) );
84
  }
85
 
86
  /**
87
- * Generates location data and saves to a post.
88
- *
89
- * @param int $job_id
90
  * @param string $location
91
  */
92
  public static function generate_location_data( $job_id, $location ) {
@@ -95,8 +60,7 @@ class WP_Job_Manager_Geocode {
95
  }
96
 
97
  /**
98
- * Deletes a job's location data.
99
- *
100
  * @param int $job_id
101
  */
102
  public static function clear_location_data( $job_id ) {
@@ -116,9 +80,8 @@ class WP_Job_Manager_Geocode {
116
  }
117
 
118
  /**
119
- * Saves any returned data to post meta.
120
- *
121
- * @param int $job_id
122
  * @param array $address_data
123
  */
124
  public static function save_location_data( $job_id, $address_data ) {
@@ -133,63 +96,15 @@ class WP_Job_Manager_Geocode {
133
  }
134
 
135
  /**
136
- * Retrieves the Google Maps API key from the plugin's settings.
137
- *
138
- * @param string $key
139
- * @return string
140
- */
141
- public function get_google_maps_api_key( $key ) {
142
- return get_option( 'job_manager_google_maps_api_key' );
143
- }
144
-
145
- /**
146
- * Adds the necessary query arguments for a Google Maps Geocode API request.
147
- *
148
- * @param string $geocode_endpoint_url
149
- * @param string $raw_address
150
- * @return string|bool
151
- */
152
- public function add_geolocation_endpoint_query_args( $geocode_endpoint_url, $raw_address ) {
153
- // Add an API key if available.
154
- $api_key = apply_filters( 'job_manager_geolocation_api_key', '', $raw_address );
155
-
156
- if ( '' !== $api_key ) {
157
- $geocode_endpoint_url = add_query_arg( 'key', rawurlencode( $api_key ), $geocode_endpoint_url );
158
- }
159
-
160
- $geocode_endpoint_url = add_query_arg( 'address', rawurlencode( $raw_address ), $geocode_endpoint_url );
161
-
162
- $locale = get_locale();
163
- if ( $locale ) {
164
- $geocode_endpoint_url = add_query_arg( 'language', substr( $locale, 0, 2 ), $geocode_endpoint_url );
165
- }
166
-
167
- $region = apply_filters( 'job_manager_geolocation_region_cctld', '', $raw_address );
168
- if ( '' !== $region ) {
169
- $geocode_endpoint_url = add_query_arg( 'region', rawurlencode( $region ), $geocode_endpoint_url );
170
- }
171
-
172
- return $geocode_endpoint_url;
173
- }
174
-
175
- /**
176
- * Gets Location Data from Google.
177
  *
178
  * Based on code by Eyal Fitoussi.
179
  *
180
  * @param string $raw_address
181
- * @return array|bool location data.
182
- * @throws Exception After geocoding error.
183
  */
184
  public static function get_location_data( $raw_address ) {
185
- $invalid_chars = array(
186
- ' ' => '+',
187
- ',' => '',
188
- '?' => '',
189
- '&' => '',
190
- '=' => '',
191
- '#' => '',
192
- );
193
  $raw_address = trim( strtolower( str_replace( array_keys( $invalid_chars ), array_values( $invalid_chars ), $raw_address ) ) );
194
 
195
  if ( empty( $raw_address ) ) {
@@ -200,44 +115,48 @@ class WP_Job_Manager_Geocode {
200
  $geocoded_address = get_transient( $transient_name );
201
  $jm_geocode_over_query_limit = get_transient( 'jm_geocode_over_query_limit' );
202
 
203
- // Query limit reached - don't geocode for a while.
204
  if ( $jm_geocode_over_query_limit && false === $geocoded_address ) {
205
  return false;
206
  }
207
 
208
- $geocode_api_url = apply_filters( 'job_manager_geolocation_endpoint', self::GOOGLE_MAPS_GEOCODE_API_URL, $raw_address );
209
- if ( false === $geocode_api_url ) {
210
- return false;
211
- }
212
-
213
  try {
214
  if ( false === $geocoded_address || empty( $geocoded_address->results[0] ) ) {
215
- $result = wp_remote_get(
216
- $geocode_api_url,
217
  array(
218
  'timeout' => 5,
219
- 'redirection' => 1,
220
- 'httpversion' => '1.1',
221
- 'user-agent' => 'WordPress/WP-Job-Manager-' . JOB_MANAGER_VERSION . '; ' . get_bloginfo( 'url' ),
222
- 'sslverify' => false,
223
- )
224
  );
225
  $result = wp_remote_retrieve_body( $result );
226
  $geocoded_address = json_decode( $result );
227
 
228
  if ( $geocoded_address->status ) {
229
- if ( 'ZERO_RESULTS' === $geocoded_address->status ) {
230
- throw new Exception( __( 'No results found', 'wp-job-manager' ) );
231
- } elseif ( 'OVER_QUERY_LIMIT' === $geocoded_address->status ) {
232
- set_transient( 'jm_geocode_over_query_limit', 1, HOUR_IN_SECONDS );
233
- throw new Exception( __( 'Query limit reached', 'wp-job-manager' ) );
234
- } elseif ( 'OK' === $geocoded_address->status && ! empty( $geocoded_address->results[0] ) ) {
235
- set_transient( $transient_name, $geocoded_address, DAY_IN_SECONDS * 7 );
236
- } else {
237
- throw new Exception( __( 'Geocoding error', 'wp-job-manager' ) );
 
 
 
 
 
 
 
 
 
238
  }
239
  } else {
240
- throw new Exception( __( 'Geocoding error', 'wp-job-manager' ) );
241
  }
242
  }
243
  } catch ( Exception $e ) {
@@ -262,29 +181,29 @@ class WP_Job_Manager_Geocode {
262
 
263
  foreach ( $address_data as $data ) {
264
  switch ( $data->types[0] ) {
265
- case 'street_number':
266
  $address['street_number'] = sanitize_text_field( $data->long_name );
267
- break;
268
- case 'route':
269
- $address['street'] = sanitize_text_field( $data->long_name );
270
- break;
271
- case 'sublocality_level_1':
272
- case 'locality':
273
- case 'postal_town':
274
- $address['city'] = sanitize_text_field( $data->long_name );
275
- break;
276
- case 'administrative_area_level_1':
277
- case 'administrative_area_level_2':
278
- $address['state_short'] = sanitize_text_field( $data->short_name );
279
- $address['state_long'] = sanitize_text_field( $data->long_name );
280
- break;
281
- case 'postal_code':
282
- $address['postcode'] = sanitize_text_field( $data->long_name );
283
- break;
284
- case 'country':
285
  $address['country_short'] = sanitize_text_field( $data->short_name );
286
  $address['country_long'] = sanitize_text_field( $data->long_name );
287
- break;
288
  }
289
  }
290
  }
@@ -293,4 +212,4 @@ class WP_Job_Manager_Geocode {
293
  }
294
  }
295
 
296
- WP_Job_Manager_Geocode::instance();
1
  <?php
2
 
3
+ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
4
 
5
  /**
6
+ * WP_Job_Manager_Geocode
7
  *
8
+ * Obtains Geolocation data for posted jobs from Google.
 
9
  */
10
  class WP_Job_Manager_Geocode {
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  /**
13
+ * Constructor
14
  */
15
  public function __construct() {
 
 
16
  add_action( 'job_manager_update_job_data', array( $this, 'update_location_data' ), 20, 2 );
17
  add_action( 'job_manager_job_location_edited', array( $this, 'change_location_data' ), 20, 2 );
18
  }
19
 
20
  /**
21
+ * Update location data - when submitting a job
 
 
 
22
  */
23
  public function update_location_data( $job_id, $values ) {
24
  if ( apply_filters( 'job_manager_geolocation_enabled', true ) && isset( $values['job']['job_location'] ) ) {
28
  }
29
 
30
  /**
31
+ * Change a jobs location data upon editing
32
+ * @param int $job_id
 
33
  * @param string $new_location
34
  */
35
  public function change_location_data( $job_id, $new_location ) {
41
  }
42
 
43
  /**
44
+ * Checks if a job has location data or not
45
+ * @param int $job_id
 
46
  * @return boolean
47
  */
48
  public static function has_location_data( $job_id ) {
49
+ return get_post_meta( $job_id, 'geolocated', true ) == 1;
50
  }
51
 
52
  /**
53
+ * Called manually to generate location data and save to a post
54
+ * @param int $job_id
 
55
  * @param string $location
56
  */
57
  public static function generate_location_data( $job_id, $location ) {
60
  }
61
 
62
  /**
63
+ * Delete a job's location data
 
64
  * @param int $job_id
65
  */
66
  public static function clear_location_data( $job_id ) {
80
  }
81
 
82
  /**
83
+ * Save any returned data to post meta
84
+ * @param int $job_id
 
85
  * @param array $address_data
86
  */
87
  public static function save_location_data( $job_id, $address_data ) {
96
  }
97
 
98
  /**
99
+ * Get Location Data from Google
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  *
101
  * Based on code by Eyal Fitoussi.
102
  *
103
  * @param string $raw_address
104
+ * @return array location data
 
105
  */
106
  public static function get_location_data( $raw_address ) {
107
+ $invalid_chars = array( " " => "+", "," => "", "?" => "", "&" => "", "=" => "" , "#" => "" );
 
 
 
 
 
 
 
108
  $raw_address = trim( strtolower( str_replace( array_keys( $invalid_chars ), array_values( $invalid_chars ), $raw_address ) ) );
109
 
110
  if ( empty( $raw_address ) ) {
115
  $geocoded_address = get_transient( $transient_name );
116
  $jm_geocode_over_query_limit = get_transient( 'jm_geocode_over_query_limit' );
117
 
118
+ // Query limit reached - don't geocode for a while
119
  if ( $jm_geocode_over_query_limit && false === $geocoded_address ) {
120
  return false;
121
  }
122
 
 
 
 
 
 
123
  try {
124
  if ( false === $geocoded_address || empty( $geocoded_address->results[0] ) ) {
125
+ $result = wp_remote_get(
126
+ apply_filters( 'job_manager_geolocation_endpoint', "http://maps.googleapis.com/maps/api/geocode/json?address=" . $raw_address . "&sensor=false&region=" . apply_filters( 'job_manager_geolocation_region_cctld', '', $raw_address ), $raw_address ),
127
  array(
128
  'timeout' => 5,
129
+ 'redirection' => 1,
130
+ 'httpversion' => '1.1',
131
+ 'user-agent' => 'WordPress/WP-Job-Manager-' . JOB_MANAGER_VERSION . '; ' . get_bloginfo( 'url' ),
132
+ 'sslverify' => false
133
+ )
134
  );
135
  $result = wp_remote_retrieve_body( $result );
136
  $geocoded_address = json_decode( $result );
137
 
138
  if ( $geocoded_address->status ) {
139
+ switch ( $geocoded_address->status ) {
140
+ case 'ZERO_RESULTS' :
141
+ throw new Exception( __( "No results found", 'wp-job-manager' ) );
142
+ break;
143
+ case 'OVER_QUERY_LIMIT' :
144
+ set_transient( 'jm_geocode_over_query_limit', 1, HOUR_IN_SECONDS );
145
+ throw new Exception( __( "Query limit reached", 'wp-job-manager' ) );
146
+ break;
147
+ case 'OK' :
148
+ if ( ! empty( $geocoded_address->results[0] ) ) {
149
+ set_transient( $transient_name, $geocoded_address, 24 * HOUR_IN_SECONDS * 365 );
150
+ } else {
151
+ throw new Exception( __( "Geocoding error", 'wp-job-manager' ) );
152
+ }
153
+ break;
154
+ default :
155
+ throw new Exception( __( "Geocoding error", 'wp-job-manager' ) );
156
+ break;
157
  }
158
  } else {
159
+ throw new Exception( __( "Geocoding error", 'wp-job-manager' ) );
160
  }
161
  }
162
  } catch ( Exception $e ) {
181
 
182
  foreach ( $address_data as $data ) {
183
  switch ( $data->types[0] ) {
184
+ case 'street_number' :
185
  $address['street_number'] = sanitize_text_field( $data->long_name );
186
+ break;
187
+ case 'route' :
188
+ $address['street'] = sanitize_text_field( $data->long_name );
189
+ break;
190
+ case 'sublocality_level_1' :
191
+ case 'locality' :
192
+ case 'postal_town' :
193
+ $address['city'] = sanitize_text_field( $data->long_name );
194
+ break;
195
+ case 'administrative_area_level_1' :
196
+ case 'administrative_area_level_2' :
197
+ $address['state_short'] = sanitize_text_field( $data->short_name );
198
+ $address['state_long'] = sanitize_text_field( $data->long_name );
199
+ break;
200
+ case 'postal_code' :
201
+ $address['postcode'] = sanitize_text_field( $data->long_name );
202
+ break;
203
+ case 'country' :
204
  $address['country_short'] = sanitize_text_field( $data->short_name );
205
  $address['country_long'] = sanitize_text_field( $data->long_name );
206
+ break;
207
  }
208
  }
209
  }
212
  }
213
  }
214
 
215
+ new WP_Job_Manager_Geocode();
includes/class-wp-job-manager-install.php CHANGED
@@ -5,39 +5,32 @@ if ( ! defined( 'ABSPATH' ) ) {
5
  }
6
 
7
  /**
8
- * Handles the installation of the WP Job Manager plugin.
9
- *
10
- * @package wp-job-manager
11
- * @since 1.0.0
12
  */
13
  class WP_Job_Manager_Install {
14
 
15
  /**
16
- * Installs WP Job Manager.
17
  */
18
  public static function install() {
19
  global $wpdb;
20
 
21
  self::init_user_roles();
22
  self::default_terms();
 
23
 
24
- // Redirect to setup screen for new installs.
25
  if ( ! get_option( 'wp_job_manager_version' ) ) {
26
  set_transient( '_job_manager_activation_redirect', 1, HOUR_IN_SECONDS );
27
  }
28
 
29
- // Update featured posts ordering.
30
  if ( version_compare( get_option( 'wp_job_manager_version', JOB_MANAGER_VERSION ), '1.22.0', '<' ) ) {
31
  $wpdb->query( "UPDATE {$wpdb->posts} p SET p.menu_order = 0 WHERE p.post_type='job_listing';" );
32
  $wpdb->query( "UPDATE {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id SET p.menu_order = -1 WHERE pm.meta_key = '_featured' AND pm.meta_value='1' AND p.post_type='job_listing';" );
33
  }
34
 
35
- // Update default term meta with employment types.
36
- if ( version_compare( get_option( 'wp_job_manager_version', JOB_MANAGER_VERSION ), '1.28.0', '<' ) ) {
37
- self::add_employment_types();
38
- }
39
-
40
- // Update legacy options.
41
  if ( false === get_option( 'job_manager_submit_job_form_page_id', false ) && get_option( 'job_manager_submit_page_slug' ) ) {
42
  $page_id = get_page_by_path( get_option( 'job_manager_submit_page_slug' ) )->ID;
43
  update_option( 'job_manager_submit_job_form_page_id', $page_id );
@@ -52,25 +45,21 @@ class WP_Job_Manager_Install {
52
  }
53
 
54
  /**
55
- * Initializes user roles.
56
  */
57
  private static function init_user_roles() {
58
  global $wp_roles;
59
 
60
  if ( class_exists( 'WP_Roles' ) && ! isset( $wp_roles ) ) {
61
- $wp_roles = new WP_Roles(); // WPCS: override ok.
62
  }
63
 
64
  if ( is_object( $wp_roles ) ) {
65
- add_role(
66
- 'employer',
67
- __( 'Employer', 'wp-job-manager' ),
68
- array(
69
- 'read' => true,
70
- 'edit_posts' => false,
71
- 'delete_posts' => false,
72
- )
73
- );
74
 
75
  $capabilities = self::get_core_capabilities();
76
 
@@ -83,55 +72,58 @@ class WP_Job_Manager_Install {
83
  }
84
 
85
  /**
86
- * Returns capabilities.
87
- *
88
  * @return array
89
  */
90
  private static function get_core_capabilities() {
91
  return array(
92
- 'core' => array(
93
- 'manage_job_listings',
94
  ),
95
  'job_listing' => array(
96
- 'edit_job_listing',
97
- 'read_job_listing',
98
- 'delete_job_listing',
99
- 'edit_job_listings',
100
- 'edit_others_job_listings',
101
- 'publish_job_listings',
102
- 'read_private_job_listings',
103
- 'delete_job_listings',
104
- 'delete_private_job_listings',
105
- 'delete_published_job_listings',
106
- 'delete_others_job_listings',
107
- 'edit_private_job_listings',
108
- 'edit_published_job_listings',
109
- 'manage_job_listing_terms',
110
- 'edit_job_listing_terms',
111
- 'delete_job_listing_terms',
112
- 'assign_job_listing_terms',
113
- ),
114
  );
115
  }
116
 
117
  /**
118
- * Sets up the default WP Job Manager terms.
119
  */
120
  private static function default_terms() {
121
- if ( 1 === intval( get_option( 'job_manager_installed_terms' ) ) ) {
122
  return;
123
  }
124
 
125
- $taxonomies = self::get_default_taxonomy_terms();
 
 
 
 
 
 
 
 
 
126
  foreach ( $taxonomies as $taxonomy => $terms ) {
127
- foreach ( $terms as $term => $meta ) {
128
  if ( ! get_term_by( 'slug', sanitize_title( $term ), $taxonomy ) ) {
129
- $tt_package = wp_insert_term( $term, $taxonomy );
130
- if ( is_array( $tt_package ) && isset( $tt_package['term_id'] ) && ! empty( $meta ) ) {
131
- foreach ( $meta as $meta_key => $meta_value ) {
132
- add_term_meta( $tt_package['term_id'], $meta_key, $meta_value );
133
- }
134
- }
135
  }
136
  }
137
  }
@@ -140,48 +132,14 @@ class WP_Job_Manager_Install {
140
  }
141
 
142
  /**
143
- * Default taxonomy terms to set up in WP Job Manager.
144
- *
145
- * @return array Default taxonomy terms.
146
  */
147
- private static function get_default_taxonomy_terms() {
148
- return array(
149
- 'job_listing_type' => array(
150
- 'Full Time' => array(
151
- 'employment_type' => 'FULL_TIME',
152
- ),
153
- 'Part Time' => array(
154
- 'employment_type' => 'PART_TIME',
155
- ),
156
- 'Temporary' => array(
157
- 'employment_type' => 'TEMPORARY',
158
- ),
159
- 'Freelance' => array(
160
- 'employment_type' => 'CONTRACTOR',
161
- ),
162
- 'Internship' => array(
163
- 'employment_type' => 'INTERN',
164
- ),
165
- ),
166
- );
167
- }
168
-
169
- /**
170
- * Adds the employment type to default job types when updating from a previous WP Job Manager version.
171
- */
172
- private static function add_employment_types() {
173
- $taxonomies = self::get_default_taxonomy_terms();
174
- $terms = $taxonomies['job_listing_type'];
175
-
176
- foreach ( $terms as $term => $meta ) {
177
- $term = get_term_by( 'slug', sanitize_title( $term ), 'job_listing_type' );
178
- if ( $term ) {
179
- foreach ( $meta as $meta_key => $meta_value ) {
180
- if ( ! get_term_meta( (int) $term->term_id, $meta_key, true ) ) {
181
- add_term_meta( (int) $term->term_id, $meta_key, $meta_value );
182
- }
183
- }
184
- }
185
- }
186
  }
187
  }
5
  }
6
 
7
  /**
8
+ * WP_Job_Manager_Install
 
 
 
9
  */
10
  class WP_Job_Manager_Install {
11
 
12
  /**
13
+ * Install WP Job Manager
14
  */
15
  public static function install() {
16
  global $wpdb;
17
 
18
  self::init_user_roles();
19
  self::default_terms();
20
+ self::schedule_cron();
21
 
22
+ // Redirect to setup screen for new installs
23
  if ( ! get_option( 'wp_job_manager_version' ) ) {
24
  set_transient( '_job_manager_activation_redirect', 1, HOUR_IN_SECONDS );
25
  }
26
 
27
+ // Update featured posts ordering
28
  if ( version_compare( get_option( 'wp_job_manager_version', JOB_MANAGER_VERSION ), '1.22.0', '<' ) ) {
29
  $wpdb->query( "UPDATE {$wpdb->posts} p SET p.menu_order = 0 WHERE p.post_type='job_listing';" );
30
  $wpdb->query( "UPDATE {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id SET p.menu_order = -1 WHERE pm.meta_key = '_featured' AND pm.meta_value='1' AND p.post_type='job_listing';" );
31
  }
32
 
33
+ // Update legacy options
 
 
 
 
 
34
  if ( false === get_option( 'job_manager_submit_job_form_page_id', false ) && get_option( 'job_manager_submit_page_slug' ) ) {
35
  $page_id = get_page_by_path( get_option( 'job_manager_submit_page_slug' ) )->ID;
36
  update_option( 'job_manager_submit_job_form_page_id', $page_id );
45
  }
46
 
47
  /**
48
+ * Init user roles
49
  */
50
  private static function init_user_roles() {
51
  global $wp_roles;
52
 
53
  if ( class_exists( 'WP_Roles' ) && ! isset( $wp_roles ) ) {
54
+ $wp_roles = new WP_Roles();
55
  }
56
 
57
  if ( is_object( $wp_roles ) ) {
58
+ add_role( 'employer', __( 'Employer', 'wp-job-manager' ), array(
59
+ 'read' => true,
60
+ 'edit_posts' => false,
61
+ 'delete_posts' => false
62
+ ) );
 
 
 
 
63
 
64
  $capabilities = self::get_core_capabilities();
65
 
72
  }
73
 
74
  /**
75
+ * Get capabilities
 
76
  * @return array
77
  */
78
  private static function get_core_capabilities() {
79
  return array(
80
+ 'core' => array(
81
+ 'manage_job_listings'
82
  ),
83
  'job_listing' => array(
84
+ "edit_job_listing",
85
+ "read_job_listing",
86
+ "delete_job_listing",
87
+ "edit_job_listings",
88
+ "edit_others_job_listings",
89
+ "publish_job_listings",
90
+ "read_private_job_listings",
91
+ "delete_job_listings",
92
+ "delete_private_job_listings",
93
+ "delete_published_job_listings",
94
+ "delete_others_job_listings",
95
+ "edit_private_job_listings",
96
+ "edit_published_job_listings",
97
+ "manage_job_listing_terms",
98
+ "edit_job_listing_terms",
99
+ "delete_job_listing_terms",
100
+ "assign_job_listing_terms"
101
+ )
102
  );
103
  }
104
 
105
  /**
106
+ * default_terms function.
107
  */
108
  private static function default_terms() {
109
+ if ( get_option( 'job_manager_installed_terms' ) == 1 ) {
110
  return;
111
  }
112
 
113
+ $taxonomies = array(
114
+ 'job_listing_type' => array(
115
+ 'Full Time',
116
+ 'Part Time',
117
+ 'Temporary',
118
+ 'Freelance',
119
+ 'Internship'
120
+ )
121
+ );
122
+
123
  foreach ( $taxonomies as $taxonomy => $terms ) {
124
+ foreach ( $terms as $term ) {
125
  if ( ! get_term_by( 'slug', sanitize_title( $term ), $taxonomy ) ) {
126
+ wp_insert_term( $term, $taxonomy );
 
 
 
 
 
127
  }
128
  }
129
  }
132
  }
133
 
134
  /**
135
+ * Setup cron jobs
 
 
136
  */
137
+ private static function schedule_cron() {
138
+ wp_clear_scheduled_hook( 'job_manager_check_for_expired_jobs' );
139
+ wp_clear_scheduled_hook( 'job_manager_delete_old_previews' );
140
+ wp_clear_scheduled_hook( 'job_manager_clear_expired_transients' );
141
+ wp_schedule_event( time(), 'hourly', 'job_manager_check_for_expired_jobs' );
142
+ wp_schedule_event( time(), 'daily', 'job_manager_delete_old_previews' );
143
+ wp_schedule_event( time(), 'twicedaily', 'job_manager_clear_expired_transients' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  }
145
  }
includes/class-wp-job-manager-post-types.php CHANGED
@@ -1,36 +1,11 @@
1
  <?php
2
  /**
3
- * Handles displays and hooks for the Job Listing custom post type.
4
- *
5
- * @package wp-job-manager
6
- * @since 1.0.0
7
  */
8
  class WP_Job_Manager_Post_Types {
9
 
10
  /**
11
- * The single instance of the class.
12
- *
13
- * @var self
14
- * @since 1.26.0
15
- */
16
- private static $_instance = null;
17
-
18
- /**
19
- * Allows for accessing single instance of class. Class should only be constructed once per call.
20
- *
21
- * @since 1.26.0
22
- * @static
23
- * @return self Main instance.
24
- */
25
- public static function instance() {
26
- if ( is_null( self::$_instance ) ) {
27
- self::$_instance = new self();
28
- }
29
- return self::$_instance;
30
- }
31
-
32
- /**
33
- * Constructor.
34
  */
35
  public function __construct() {
36
  add_action( 'init', array( $this, 'register_post_types' ), 0 );
@@ -44,19 +19,12 @@ class WP_Job_Manager_Post_Types {
44
  add_action( 'auto-draft_to_publish', array( $this, 'set_expiry' ) );
45
  add_action( 'expired_to_publish', array( $this, 'set_expiry' ) );
46
 
47
- add_action( 'wp_head', array( $this, 'noindex_expired_filled_job_listings' ) );
48
- add_action( 'wp_footer', array( $this, 'output_structured_data' ) );
49
-
50
- add_filter( 'the_job_description', 'wptexturize' );
51
- add_filter( 'the_job_description', 'convert_smilies' );
52
- add_filter( 'the_job_description', 'convert_chars' );
53
- add_filter( 'the_job_description', 'wpautop' );
54
- add_filter( 'the_job_description', 'shortcode_unautop' );
55
  add_filter( 'the_job_description', 'prepend_attachment' );
56
- if ( ! empty( $GLOBALS['wp_embed'] ) ) {
57
- add_filter( 'the_job_description', array( $GLOBALS['wp_embed'], 'run_shortcode' ), 8 );
58
- add_filter( 'the_job_description', array( $GLOBALS['wp_embed'], 'autoembed' ), 8 );
59
- }
60
 
61
  add_action( 'job_manager_application_details_email', array( $this, 'application_details_email' ) );
62
  add_action( 'job_manager_application_details_url', array( $this, 'application_details_url' ) );
@@ -66,227 +34,182 @@ class WP_Job_Manager_Post_Types {
66
  add_action( 'update_post_meta', array( $this, 'update_post_meta' ), 10, 4 );
67
  add_action( 'wp_insert_post', array( $this, 'maybe_add_default_meta_data' ), 10, 2 );
68
 
69
- add_action( 'parse_query', array( $this, 'add_feed_query_args' ) );
 
70
 
71
- // Single job content.
 
 
 
 
 
72
  $this->job_content_filter( true );
73
  }
74
 
75
  /**
76
- * Registers the custom post type and taxonomies.
 
 
 
77
  */
78
  public function register_post_types() {
79
- if ( post_type_exists( 'job_listing' ) ) {
80
  return;
81
- }
82
 
83
  $admin_capability = 'manage_job_listings';
84
 
85
- $permalink_structure = WP_Job_Manager_Post_Types::get_permalink_structure();
86
-
87
  /**
88
  * Taxonomies
89
  */
90
  if ( get_option( 'job_manager_enable_categories' ) ) {
91
- $singular = __( 'Job category', 'wp-job-manager' );
92
- $plural = __( 'Job categories', 'wp-job-manager' );
93
 
94
  if ( current_theme_supports( 'job-manager-templates' ) ) {
95
- $rewrite = array(
96
- 'slug' => $permalink_structure['category_rewrite_slug'],
97
  'with_front' => false,
98
- 'hierarchical' => false,
99
  );
100
- $public = true;
101
  } else {
102
- $rewrite = false;
103
- $public = false;
104
  }
105
 
106
- register_taxonomy(
107
- 'job_listing_category',
108
  apply_filters( 'register_taxonomy_job_listing_category_object_type', array( 'job_listing' ) ),
109
- apply_filters(
110
- 'register_taxonomy_job_listing_category_args',
111
- array(
112
- 'hierarchical' => true,
113
- 'update_count_callback' => '_update_post_term_count',
114
- 'label' => $plural,
115
- 'labels' => array(
116
- 'name' => $plural,
117
- 'singular_name' => $singular,
118
- 'menu_name' => ucwords( $plural ),
119
- // translators: Placeholder %s is the plural label of the job listing category taxonomy type.
120
- 'search_items' => sprintf( __( 'Search %s', 'wp-job-manager' ), $plural ),
121
- // translators: Placeholder %s is the plural label of the job listing category taxonomy type.
122
- 'all_items' => sprintf( __( 'All %s', 'wp-job-manager' ), $plural ),
123
- // translators: Placeholder %s is the singular label of the job listing category taxonomy type.
124
- 'parent_item' => sprintf( __( 'Parent %s', 'wp-job-manager' ), $singular ),
125
- // translators: Placeholder %s is the singular label of the job listing category taxonomy type.
126
- 'parent_item_colon' => sprintf( __( 'Parent %s:', 'wp-job-manager' ), $singular ),
127
- // translators: Placeholder %s is the singular label of the job listing category taxonomy type.
128
- 'edit_item' => sprintf( __( 'Edit %s', 'wp-job-manager' ), $singular ),
129
- // translators: Placeholder %s is the singular label of the job listing category taxonomy type.
130
- 'update_item' => sprintf( __( 'Update %s', 'wp-job-manager' ), $singular ),
131
- // translators: Placeholder %s is the singular label of the job listing category taxonomy type.
132
- 'add_new_item' => sprintf( __( 'Add New %s', 'wp-job-manager' ), $singular ),
133
- // translators: Placeholder %s is the singular label of the job listing category taxonomy type.
134
- 'new_item_name' => sprintf( __( 'New %s Name', 'wp-job-manager' ), $singular ),
135
- ),
136
- 'show_ui' => true,
137
- 'show_tagcloud' => false,
138
- 'public' => $public,
139
- 'capabilities' => array(
140
- 'manage_terms' => $admin_capability,
141
- 'edit_terms' => $admin_capability,
142
- 'delete_terms' => $admin_capability,
143
- 'assign_terms' => $admin_capability,
144
- ),
145
- 'rewrite' => $rewrite,
146
- )
147
- )
148
  );
149
- }
150
-
151
- if ( get_option( 'job_manager_enable_types' ) ) {
152
- $singular = __( 'Job type', 'wp-job-manager' );
153
- $plural = __( 'Job types', 'wp-job-manager' );
154
-
155
- if ( current_theme_supports( 'job-manager-templates' ) ) {
156
- $rewrite = array(
157
- 'slug' => $permalink_structure['type_rewrite_slug'],
158
- 'with_front' => false,
159
- 'hierarchical' => false,
160
- );
161
- $public = true;
162
- } else {
163
- $rewrite = false;
164
- $public = false;
165
- }
166
-
167
- register_taxonomy(
168
- 'job_listing_type',
169
- apply_filters( 'register_taxonomy_job_listing_type_object_type', array( 'job_listing' ) ),
170
- apply_filters(
171
- 'register_taxonomy_job_listing_type_args',
172
- array(
173
- 'hierarchical' => true,
174
- 'label' => $plural,
175
- 'labels' => array(
176
- 'name' => $plural,
177
- 'singular_name' => $singular,
178
- 'menu_name' => ucwords( $plural ),
179
- // translators: Placeholder %s is the plural label of the job listing job type taxonomy type.
180
- 'search_items' => sprintf( __( 'Search %s', 'wp-job-manager' ), $plural ),
181
- // translators: Placeholder %s is the plural label of the job listing job type taxonomy type.
182
- 'all_items' => sprintf( __( 'All %s', 'wp-job-manager' ), $plural ),
183
- // translators: Placeholder %s is the singular label of the job listing job type taxonomy type.
184
- 'parent_item' => sprintf( __( 'Parent %s', 'wp-job-manager' ), $singular ),
185
- // translators: Placeholder %s is the singular label of the job listing job type taxonomy type.
186
- 'parent_item_colon' => sprintf( __( 'Parent %s:', 'wp-job-manager' ), $singular ),
187
- // translators: Placeholder %s is the singular label of the job listing job type taxonomy type.
188
- 'edit_item' => sprintf( __( 'Edit %s', 'wp-job-manager' ), $singular ),
189
- // translators: Placeholder %s is the singular label of the job listing job type taxonomy type.
190
- 'update_item' => sprintf( __( 'Update %s', 'wp-job-manager' ), $singular ),
191
- // translators: Placeholder %s is the singular label of the job listing job type taxonomy type.
192
- 'add_new_item' => sprintf( __( 'Add New %s', 'wp-job-manager' ), $singular ),
193
- // translators: Placeholder %s is the singular label of the job listing job type taxonomy type.
194
- 'new_item_name' => sprintf( __( 'New %s Name', 'wp-job-manager' ), $singular ),
195
- ),
196
- 'show_ui' => true,
197
- 'show_tagcloud' => false,
198
- 'public' => $public,
199
- 'capabilities' => array(
200
- 'manage_terms' => $admin_capability,
201
- 'edit_terms' => $admin_capability,
202
- 'delete_terms' => $admin_capability,
203
- 'assign_terms' => $admin_capability,
204
- ),
205
- 'rewrite' => $rewrite,
206
- )
207
- )
208
- );
209
- }
210
-
211
- /**
212
  * Post types
213
  */
214
- $singular = __( 'Job', 'wp-job-manager' );
215
- $plural = __( 'Jobs', 'wp-job-manager' );
216
 
217
- /**
218
- * Set whether to add archive page support when registering the job listing post type.
219
- *
220
- * @since 1.30.0
221
- *
222
- * @param bool $enable_job_archive_page
223
- */
224
- if ( apply_filters( 'job_manager_enable_job_archive_page', current_theme_supports( 'job-manager-templates' ) ) ) {
225
  $has_archive = _x( 'jobs', 'Post type archive slug - resave permalinks after changing this', 'wp-job-manager' );
226
  } else {
227
  $has_archive = false;
228
  }
229
 
230
- $rewrite = array(
231
- 'slug' => $permalink_structure['job_rewrite_slug'],
232
  'with_front' => false,
233
  'feeds' => true,
234
- 'pages' => false,
235
  );
236
 
237
- register_post_type(
238
- 'job_listing',
239
- apply_filters(
240
- 'register_post_type_job_listing',
241
- array(
242
- 'labels' => array(
243
- 'name' => $plural,
244
- 'singular_name' => $singular,
245
- 'menu_name' => __( 'Job Listings', 'wp-job-manager' ),
246
- // translators: Placeholder %s is the plural label of the job listing post type.
247
- 'all_items' => sprintf( __( 'All %s', 'wp-job-manager' ), $plural ),
248
- 'add_new' => __( 'Add New', 'wp-job-manager' ),
249
- // translators: Placeholder %s is the singular label of the job listing post type.
250
- 'add_new_item' => sprintf( __( 'Add %s', 'wp-job-manager' ), $singular ),
251
- 'edit' => __( 'Edit', 'wp-job-manager' ),
252
- // translators: Placeholder %s is the singular label of the job listing post type.
253
- 'edit_item' => sprintf( __( 'Edit %s', 'wp-job-manager' ), $singular ),
254
- // translators: Placeholder %s is the singular label of the job listing post type.
255
- 'new_item' => sprintf( __( 'New %s', 'wp-job-manager' ), $singular ),
256
- // translators: Placeholder %s is the singular label of the job listing post type.
257
- 'view' => sprintf( __( 'View %s', 'wp-job-manager' ), $singular ),
258
- // translators: Placeholder %s is the singular label of the job listing post type.
259
- 'view_item' => sprintf( __( 'View %s', 'wp-job-manager' ), $singular ),
260
- // translators: Placeholder %s is the singular label of the job listing post type.
261
- 'search_items' => sprintf( __( 'Search %s', 'wp-job-manager' ), $plural ),
262
- // translators: Placeholder %s is the singular label of the job listing post type.
263
- 'not_found' => sprintf( __( 'No %s found', 'wp-job-manager' ), $plural ),
264
- // translators: Placeholder %s is the plural label of the job listing post type.
265
- 'not_found_in_trash' => sprintf( __( 'No %s found in trash', 'wp-job-manager' ), $plural ),
266
- // translators: Placeholder %s is the singular label of the job listing post type.
267
- 'parent' => sprintf( __( 'Parent %s', 'wp-job-manager' ), $singular ),
268
- 'featured_image' => __( 'Company Logo', 'wp-job-manager' ),
269
- 'set_featured_image' => __( 'Set company logo', 'wp-job-manager' ),
270
- 'remove_featured_image' => __( 'Remove company logo', 'wp-job-manager' ),
271
- 'use_featured_image' => __( 'Use as company logo', 'wp-job-manager' ),
272
- ),
273
- // translators: Placeholder %s is the plural label of the job listing post type.
274
- 'description' => sprintf( __( 'This is where you can create and manage %s.', 'wp-job-manager' ), $plural ),
275
- 'public' => true,
276
- 'show_ui' => true,
277
- 'capability_type' => 'job_listing',
278
- 'map_meta_cap' => true,
279
- 'publicly_queryable' => true,
280
- 'exclude_from_search' => false,
281
- 'hierarchical' => false,
282
- 'rewrite' => $rewrite,
283
- 'query_var' => true,
284
- 'supports' => array( 'title', 'editor', 'custom-fields', 'publicize', 'thumbnail' ),
285
- 'has_archive' => $has_archive,
286
- 'show_in_nav_menus' => false,
287
- 'delete_with_user' => true,
288
- )
289
- )
290
  );
291
 
292
  /**
@@ -297,62 +220,48 @@ class WP_Job_Manager_Post_Types {
297
  /**
298
  * Post status
299
  */
300
- register_post_status(
301
- 'expired',
302
- array(
303
- 'label' => _x( 'Expired', 'post status', 'wp-job-manager' ),
304
- 'public' => true,
305
- 'protected' => true,
306
- 'exclude_from_search' => true,
307
- 'show_in_admin_all_list' => true,
308
- 'show_in_admin_status_list' => true,
309
- // translators: Placeholder %s is the number of expired posts of this type.
310
- 'label_count' => _n_noop( 'Expired <span class="count">(%s)</span>', 'Expired <span class="count">(%s)</span>', 'wp-job-manager' ),
311
- )
312
- );
313
- register_post_status(
314
- 'preview',
315
- array(
316
- 'label' => _x( 'Preview', 'post status', 'wp-job-manager' ),
317
- 'public' => false,
318
- 'exclude_from_search' => true,
319
- 'show_in_admin_all_list' => false,
320
- 'show_in_admin_status_list' => true,
321
- // translators: Placeholder %s is the number of posts in a preview state.
322
- 'label_count' => _n_noop( 'Preview <span class="count">(%s)</span>', 'Preview <span class="count">(%s)</span>', 'wp-job-manager' ),
323
- )
324
- );
325
  }
326
 
327
  /**
328
- * Change label for admin menu item to show number of Job Listing items pending approval.
329
  */
330
  public function admin_head() {
331
  global $menu;
332
 
333
- $pending_jobs = WP_Job_Manager_Cache_Helper::get_listings_count();
334
-
335
- // No need to go further if no pending jobs, menu is not set, or is not an array.
336
- if ( empty( $pending_jobs ) || empty( $menu ) || ! is_array( $menu ) ) {
337
- return;
338
- }
339
 
340
- // Try to pull menu_name from post type object to support themes/plugins that change the menu string.
341
- $post_type = get_post_type_object( 'job_listing' );
342
- $plural = isset( $post_type->labels, $post_type->labels->menu_name ) ? $post_type->labels->menu_name : __( 'Job Listings', 'wp-job-manager' );
343
-
344
- foreach ( $menu as $key => $menu_item ) {
345
- if ( strpos( $menu_item[0], $plural ) === 0 ) {
346
- $menu[ $key ][0] .= " <span class='awaiting-mod update-plugins count-" . esc_attr( $pending_jobs ) . "'><span class='pending-count'>" . absint( number_format_i18n( $pending_jobs ) ) . '</span></span>'; // WPCS: override ok.
347
- break;
348
  }
349
  }
350
  }
351
 
352
  /**
353
- * Toggles content filter on and off.
354
- *
355
- * @param bool $enable
356
  */
357
  private function job_content_filter( $enable ) {
358
  if ( ! $enable ) {
@@ -363,10 +272,7 @@ class WP_Job_Manager_Post_Types {
363
  }
364
 
365
  /**
366
- * Adds extra content before/after the post for single job listings.
367
- *
368
- * @param string $content
369
- * @return string
370
  */
371
  public function job_content( $content ) {
372
  global $post;
@@ -391,19 +297,16 @@ class WP_Job_Manager_Post_Types {
391
  }
392
 
393
  /**
394
- * Generates the RSS feed for Job Listings.
395
  */
396
  public function job_feed() {
397
- global $job_manager_keyword;
398
-
399
  $query_args = array(
400
  'post_type' => 'job_listing',
401
  'post_status' => 'publish',
402
  'ignore_sticky_posts' => 1,
403
  'posts_per_page' => isset( $_GET['posts_per_page'] ) ? absint( $_GET['posts_per_page'] ) : 10,
404
- 'paged' => absint( get_query_var( 'paged', 1 ) ),
405
  'tax_query' => array(),
406
- 'meta_query' => array(),
407
  );
408
 
409
  if ( ! empty( $_GET['search_location'] ) ) {
@@ -413,7 +316,7 @@ class WP_Job_Manager_Post_Types {
413
  $location_search[] = array(
414
  'key' => $meta_key,
415
  'value' => sanitize_text_field( $_GET['search_location'] ),
416
- 'compare' => 'like',
417
  );
418
  }
419
  $query_args['meta_query'][] = $location_search;
@@ -423,27 +326,26 @@ class WP_Job_Manager_Post_Types {
423
  $query_args['tax_query'][] = array(
424
  'taxonomy' => 'job_listing_type',
425
  'field' => 'slug',
426
- 'terms' => explode( ',', sanitize_text_field( $_GET['job_types'] ) ) + array( 0 ),
427
  );
428
  }
429
 
430
  if ( ! empty( $_GET['job_categories'] ) ) {
431
- $cats = explode( ',', sanitize_text_field( $_GET['job_categories'] ) ) + array( 0 );
432
- $field = is_numeric( $cats ) ? 'term_id' : 'slug';
433
- $operator = 'all' === get_option( 'job_manager_category_filter_type', 'all' ) && count( $cats ) > 1 ? 'AND' : 'IN';
434
  $query_args['tax_query'][] = array(
435
  'taxonomy' => 'job_listing_category',
436
  'field' => $field,
437
  'terms' => $cats,
438
- 'include_children' => 'AND' !== $operator,
439
- 'operator' => $operator,
440
  );
441
  }
442
 
443
- $job_manager_keyword = isset( $_GET['search_keywords'] ) ? sanitize_text_field( $_GET['search_keywords'] ) : '';
444
- if ( ! empty( $job_manager_keyword ) ) {
445
- $query_args['s'] = $job_manager_keyword;
446
- add_filter( 'posts_search', 'get_job_listings_keyword_search' );
447
  }
448
 
449
  if ( empty( $query_args['meta_query'] ) ) {
@@ -454,113 +356,73 @@ class WP_Job_Manager_Post_Types {
454
  unset( $query_args['tax_query'] );
455
  }
456
 
457
- query_posts( apply_filters( 'job_feed_args', $query_args ) ); // phpcs:ignore WordPress.WP.DiscouragedFunctions
458
  add_action( 'rss2_ns', array( $this, 'job_feed_namespace' ) );
459
  add_action( 'rss2_item', array( $this, 'job_feed_item' ) );
460
  do_feed_rss2( false );
461
- remove_filter( 'posts_search', 'get_job_listings_keyword_search' );
462
  }
463
 
464
  /**
465
- * Adds query arguments in order to make sure that the feed properly queries the 'job_listing' type.
466
- *
467
- * @param WP_Query $wp
468
- */
469
- public function add_feed_query_args( $wp ) {
470
-
471
- // Let's leave if not the job feed.
472
- if ( ! isset( $wp->query_vars['feed'] ) || 'job_feed' !== $wp->query_vars['feed'] ) {
473
- return;
474
- }
475
-
476
- // Leave if not a feed.
477
- if ( false === $wp->is_feed ) {
478
- return;
479
- }
480
-
481
- // If the post_type was already set, let's get out of here.
482
- if ( isset( $wp->query_vars['post_type'] ) && ! empty( $wp->query_vars['post_type'] ) ) {
483
- return;
484
- }
485
-
486
- $wp->query_vars['post_type'] = 'job_listing';
487
- }
488
-
489
- /**
490
- * Adds a custom namespace to the job feed.
491
  */
492
  public function job_feed_namespace() {
493
- echo 'xmlns:job_listing="' . esc_url( site_url() ) . '"' . "\n";
494
  }
495
 
496
  /**
497
- * Adds custom data to the job feed.
498
  */
499
  public function job_feed_item() {
500
- $post_id = get_the_ID();
501
- $location = get_the_job_location( $post_id );
502
- $company = get_the_company_name( $post_id );
503
- $job_types = wpjm_get_the_job_types( $post_id );
504
 
505
  if ( $location ) {
506
- echo '<job_listing:location><![CDATA[' . esc_html( $location ) . "]]></job_listing:location>\n";
507
  }
508
- if ( ! empty( $job_types ) ) {
509
- $job_types_names = implode( ', ', wp_list_pluck( $job_types, 'name' ) );
510
- echo '<job_listing:job_type><![CDATA[' . esc_html( $job_types_names ) . "]]></job_listing:job_type>\n";
511
  }
512
  if ( $company ) {
513
- echo '<job_listing:company><![CDATA[' . esc_html( $company ) . "]]></job_listing:company>\n";
514
  }
515
-
516
- /**
517
- * Fires at the end of each job RSS feed item.
518
- *
519
- * @param int $post_id The post ID of the job.
520
- */
521
- do_action( 'job_feed_item', $post_id );
522
  }
523
 
524
  /**
525
- * Maintenance task to expire jobs.
526
  */
527
  public function check_for_expired_jobs() {
528
  global $wpdb;
529
 
530
- // Change status to expired.
531
- $job_ids = $wpdb->get_col(
532
- $wpdb->prepare( "
533
- SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
534
- LEFT JOIN {$wpdb->posts} as posts ON postmeta.post_id = posts.ID
535
- WHERE postmeta.meta_key = '_job_expires'
536
- AND postmeta.meta_value > 0
537
- AND postmeta.meta_value < %s
538
- AND posts.post_status = 'publish'
539
- AND posts.post_type = 'job_listing'",
540
- date( 'Y-m-d', current_time( 'timestamp' ) )
541
- )
542
- );
543
 
544
  if ( $job_ids ) {
545
  foreach ( $job_ids as $job_id ) {
546
- $job_data = array();
547
- $job_data['ID'] = $job_id;
548
  $job_data['post_status'] = 'expired';
549
  wp_update_post( $job_data );
550
  }
551
  }
552
 
553
- // Delete old expired jobs.
554
  if ( apply_filters( 'job_manager_delete_expired_jobs', false ) ) {
555
- $job_ids = $wpdb->get_col(
556
- $wpdb->prepare( "
557
- SELECT posts.ID FROM {$wpdb->posts} as posts
558
- WHERE posts.post_type = 'job_listing'
559
- AND posts.post_modified < %s
560
- AND posts.post_status = 'expired'",
561
- date( 'Y-m-d', strtotime( '-' . apply_filters( 'job_manager_delete_expired_jobs_days', 30 ) . ' days', current_time( 'timestamp' ) ) )
562
- )
563
- );
564
 
565
  if ( $job_ids ) {
566
  foreach ( $job_ids as $job_id ) {
@@ -571,21 +433,18 @@ class WP_Job_Manager_Post_Types {
571
  }
572
 
573
  /**
574
- * Deletes old previewed jobs after 30 days to keep the DB clean.
575
  */
576
  public function delete_old_previews() {
577
  global $wpdb;
578
 
579
- // Delete old expired jobs.
580
- $job_ids = $wpdb->get_col(
581
- $wpdb->prepare( "
582
- SELECT posts.ID FROM {$wpdb->posts} as posts
583
- WHERE posts.post_type = 'job_listing'
584
- AND posts.post_modified < %s
585
- AND posts.post_status = 'preview'",
586
- date( 'Y-m-d', strtotime( '-30 days', current_time( 'timestamp' ) ) )
587
- )
588
- );
589
 
590
  if ( $job_ids ) {
591
  foreach ( $job_ids as $job_id ) {
@@ -595,126 +454,77 @@ class WP_Job_Manager_Post_Types {
595
  }
596
 
597
  /**
598
- * Typo wrapper for `set_expiry` method.
599
- *
600
- * @param WP_Post $post
601
- * @since 1.0.0
602
- * @deprecated 1.0.1
603
  */
604
  public function set_expirey( $post ) {
605
- _deprecated_function( __METHOD__, '1.0.1', 'WP_Job_Manager_Post_Types::set_expiry' );
606
  $this->set_expiry( $post );
607
  }
608
 
609
  /**
610
- * Sets expiry date when job status changes.
611
- *
612
- * @param WP_Post $post
613
  */
614
  public function set_expiry( $post ) {
615
- if ( 'job_listing' !== $post->post_type ) {
616
  return;
617
  }
618
 
619
- // See if it is already set.
620
  if ( metadata_exists( 'post', $post->ID, '_job_expires' ) ) {
621
  $expires = get_post_meta( $post->ID, '_job_expires', true );
622
  if ( $expires && strtotime( $expires ) < current_time( 'timestamp' ) ) {
623
  update_post_meta( $post->ID, '_job_expires', '' );
 
624
  }
 
625
  }
626
 
627
- // See if the user has set the expiry manually.
628
- if ( ! empty( $_POST['_job_expires'] ) ) {
629
- update_post_meta( $post->ID, '_job_expires', date( 'Y-m-d', strtotime( sanitize_text_field( $_POST['_job_expires'] ) ) ) );
630
- } elseif ( ! isset( $expires ) ) {
631
- // No manual setting? Lets generate a date if there isn't already one.
 
 
632
  $expires = calculate_job_expiry( $post->ID );
633
  update_post_meta( $post->ID, '_job_expires', $expires );
634
 
635
- // In case we are saving a post, ensure post data is updated so the field is not overridden.
636
- if ( isset( $_POST['_job_expires'] ) ) {
637
- $_POST['_job_expires'] = $expires;
638
  }
639
  }
640
  }
641
 
642
  /**
643
- * Displays the application content when the application method is an email.
644
- *
645
- * @param stdClass $apply
646
  */
647
  public function application_details_email( $apply ) {
648
  get_job_manager_template( 'job-application-email.php', array( 'apply' => $apply ) );
649
  }
650
 
651
  /**
652
- * Displays the application content when the application method is a url.
653
- *
654
- * @param stdClass $apply
655
  */
656
  public function application_details_url( $apply ) {
657
  get_job_manager_template( 'job-application-url.php', array( 'apply' => $apply ) );
658
  }
659
 
660
  /**
661
- * Fixes post name when wp_update_post changes it.
662
- *
663
- * @param array $data
664
- * @param array $postarr
665
  * @return array
666
  */
667
  public function fix_post_name( $data, $postarr ) {
668
- if ( 'job_listing' === $data['post_type']
669
- && 'pending' === $data['post_status']
670
- && ! current_user_can( 'publish_posts' )
671
- && isset( $postarr['post_name'] )
672
- ) {
673
- $data['post_name'] = $postarr['post_name'];
674
- }
675
- return $data;
676
- }
677
-
678
- /**
679
- * Retrieves permalink settings.
680
- *
681
- * @see https://github.com/woocommerce/woocommerce/blob/3.0.8/includes/wc-core-functions.php#L1573
682
- * @since 1.28.0
683
- * @return array
684
- */
685
- public static function get_permalink_structure() {
686
- // Switch to the site's default locale, bypassing the active user's locale.
687
- if ( function_exists( 'switch_to_locale' ) && did_action( 'admin_init' ) ) {
688
- switch_to_locale( get_locale() );
689
- }
690
-
691
- $permalinks = wp_parse_args(
692
- (array) get_option( 'wpjm_permalinks', array() ),
693
- array(
694
- 'job_base' => '',
695
- 'category_base' => '',
696
- 'type_base' => '',
697
- )
698
- );
699
-
700
- // Ensure rewrite slugs are set.
701
- $permalinks['job_rewrite_slug'] = untrailingslashit( empty( $permalinks['job_base'] ) ? _x( 'job', 'Job permalink - resave permalinks after changing this', 'wp-job-manager' ) : $permalinks['job_base'] );
702
- $permalinks['category_rewrite_slug'] = untrailingslashit( empty( $permalinks['category_base'] ) ? _x( 'job-category', 'Job category slug - resave permalinks after changing this', 'wp-job-manager' ) : $permalinks['category_base'] );
703
- $permalinks['type_rewrite_slug'] = untrailingslashit( empty( $permalinks['type_base'] ) ? _x( 'job-type', 'Job type slug - resave permalinks after changing this', 'wp-job-manager' ) : $permalinks['type_base'] );
704
-
705
- // Restore the original locale.
706
- if ( function_exists( 'restore_current_locale' ) && did_action( 'admin_init' ) ) {
707
- restore_current_locale();
708
- }
709
- return $permalinks;
710
  }
711
 
712
  /**
713
- * Generates location data if a post is added.
714
- *
715
- * @param int $object_id
716
- * @param string $meta_key
717
- * @param mixed $meta_value
718
  */
719
  public function maybe_add_geolocation_data( $object_id, $meta_key, $meta_value ) {
720
  if ( '_job_location' !== $meta_key || 'job_listing' !== get_post_type( $object_id ) ) {
@@ -724,88 +534,55 @@ class WP_Job_Manager_Post_Types {
724
  }
725
 
726
  /**
727
- * Triggered when updating meta on a job listing.
728
- *
729
- * @param int $meta_id
730
- * @param int $object_id
731
- * @param string $meta_key
732
- * @param mixed $meta_value
733
  */
734
  public function update_post_meta( $meta_id, $object_id, $meta_key, $meta_value ) {
735
  if ( 'job_listing' === get_post_type( $object_id ) ) {
736
  switch ( $meta_key ) {
737
- case '_job_location':
738
  $this->maybe_update_geolocation_data( $meta_id, $object_id, $meta_key, $meta_value );
739
- break;
740
- case '_featured':
741
  $this->maybe_update_menu_order( $meta_id, $object_id, $meta_key, $meta_value );
742
- break;
743
  }
744
  }
745
  }
746
 
747
  /**
748
- * Generates location data if a post is updated.
749
- *
750
- * @param int $meta_id (Unused).
751
- * @param int $object_id
752
- * @param string $meta_key (Unused).
753
- * @param mixed $meta_value
754
  */
755
  public function maybe_update_geolocation_data( $meta_id, $object_id, $meta_key, $meta_value ) {
756
  do_action( 'job_manager_job_location_edited', $object_id, $meta_value );
757
  }
758
 
759
  /**
760
- * Maybe sets menu_order if the featured status of a job is changed.
761
- *
762
- * @param int $meta_id (Unused).
763
- * @param int $object_id
764
- * @param string $meta_key (Unused).
765
- * @param mixed $meta_value
766
  */
767
  public function maybe_update_menu_order( $meta_id, $object_id, $meta_key, $meta_value ) {
768
  global $wpdb;
769
 
770
- if ( 1 === intval( $meta_value ) ) {
771
- $wpdb->update(
772
- $wpdb->posts,
773
- array( 'menu_order' => -1 ),
774
- array( 'ID' => $object_id )
775
- );
776
  } else {
777
- $wpdb->update(
778
- $wpdb->posts,
779
- array( 'menu_order' => 0 ),
780
- array(
781
- 'ID' => $object_id,
782
- 'menu_order' => -1,
783
- )
784
- );
785
  }
786
 
787
  clean_post_cache( $object_id );
788
  }
789
 
790
  /**
791
- * Legacy.
792
- *
793
- * @param int $meta_id
794
- * @param int $object_id
795
- * @param string $meta_key
796
- * @param mixed $meta_value
797
  * @deprecated 1.19.1
798
  */
799
  public function maybe_generate_geolocation_data( $meta_id, $object_id, $meta_key, $meta_value ) {
800
- _deprecated_function( __METHOD__, '1.19.1', 'WP_Job_Manager_Post_Types::maybe_update_geolocation_data' );
801
  $this->maybe_update_geolocation_data( $meta_id, $object_id, $meta_key, $meta_value );
802
  }
803
 
804
  /**
805
- * Maybe sets default meta data for job listings.
806
- *
807
- * @param int $post_id
808
- * @param WP_Post|string $post
809
  */
810
  public function maybe_add_default_meta_data( $post_id, $post = '' ) {
811
  if ( empty( $post ) || 'job_listing' === $post->post_type ) {
@@ -815,41 +592,58 @@ class WP_Job_Manager_Post_Types {
815
  }
816
 
817
  /**
818
- * Add noindex for expired and filled job listings.
 
819
  */
820
- public function noindex_expired_filled_job_listings() {
821
- if ( ! is_single() ) {
822
- return;
823
- }
824
-
825
- $post = get_post();
826
- if ( ! $post || 'job_listing' !== $post->post_type ) {
827
- return;
828
- }
829
-
830
- if ( wpjm_allow_indexing_job_listing() ) {
831
- return;
832
  }
833
-
834
- wp_no_robots();
835
  }
836
 
837
  /**
838
- * Add structured data to the footer of job listing pages.
 
 
 
 
839
  */
840
- public function output_structured_data() {
841
- if ( ! is_single() ) {
842
- return;
843
  }
 
 
844
 
845
- if ( ! wpjm_output_job_listing_structured_data() ) {
846
- return;
 
 
 
 
 
 
 
 
 
847
  }
 
 
848
 
849
- $structured_data = wpjm_get_job_listing_structured_data();
850
- if ( ! empty( $structured_data ) ) {
851
- echo '<!-- WP Job Manager Structured Data -->' . "\r\n";
852
- echo '<script type="application/ld+json">' . wp_json_encode( $structured_data ) . '</script>';
 
 
 
 
 
 
853
  }
 
854
  }
855
  }
1
  <?php
2
  /**
3
+ * WP_Job_Manager_Content class.
 
 
 
4
  */
5
  class WP_Job_Manager_Post_Types {
6
 
7
  /**
8
+ * Constructor
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  */
10
  public function __construct() {
11
  add_action( 'init', array( $this, 'register_post_types' ), 0 );
19
  add_action( 'auto-draft_to_publish', array( $this, 'set_expiry' ) );
20
  add_action( 'expired_to_publish', array( $this, 'set_expiry' ) );
21
 
22
+ add_filter( 'the_job_description', 'wptexturize' );
23
+ add_filter( 'the_job_description', 'convert_smilies' );
24
+ add_filter( 'the_job_description', 'convert_chars' );
25
+ add_filter( 'the_job_description', 'wpautop' );
26
+ add_filter( 'the_job_description', 'shortcode_unautop' );
 
 
 
27
  add_filter( 'the_job_description', 'prepend_attachment' );
 
 
 
 
28
 
29
  add_action( 'job_manager_application_details_email', array( $this, 'application_details_email' ) );
30
  add_action( 'job_manager_application_details_url', array( $this, 'application_details_url' ) );
34
  add_action( 'update_post_meta', array( $this, 'update_post_meta' ), 10, 4 );
35
  add_action( 'wp_insert_post', array( $this, 'maybe_add_default_meta_data' ), 10, 2 );
36
 
37
+ // WP ALL Import
38
+ add_action( 'pmxi_saved_post', array( $this, 'pmxi_saved_post' ), 10, 1 );
39
 
40
+ // RP4WP
41
+ add_filter( 'rp4wp_get_template', array( $this, 'rp4wp_template' ), 10, 3 );
42
+ add_filter( 'rp4wp_related_meta_fields', array( $this, 'rp4wp_related_meta_fields' ), 10, 3 );
43
+ add_filter( 'rp4wp_related_meta_fields_weight', array( $this, 'rp4wp_related_meta_fields_weight' ), 10, 3 );
44
+
45
+ // Single job content
46
  $this->job_content_filter( true );
47
  }
48
 
49
  /**
50
+ * register_post_types function.
51
+ *
52
+ * @access public
53
+ * @return void
54
  */
55
  public function register_post_types() {
56
+ if ( post_type_exists( "job_listing" ) )
57
  return;
 
58
 
59
  $admin_capability = 'manage_job_listings';
60
 
 
 
61
  /**
62
  * Taxonomies
63
  */
64
  if ( get_option( 'job_manager_enable_categories' ) ) {
65
+ $singular = __( 'Job category', 'wp-job-manager' );
66
+ $plural = __( 'Job categories', 'wp-job-manager' );
67
 
68
  if ( current_theme_supports( 'job-manager-templates' ) ) {
69
+ $rewrite = array(
70
+ 'slug' => _x( 'job-category', 'Job category slug - resave permalinks after changing this', 'wp-job-manager' ),
71
  'with_front' => false,
72
+ 'hierarchical' => false
73
  );
74
+ $public = true;
75
  } else {
76
+ $rewrite = false;
77
+ $public = false;
78
  }
79
 
80
+ register_taxonomy( "job_listing_category",
 
81
  apply_filters( 'register_taxonomy_job_listing_category_object_type', array( 'job_listing' ) ),
82
+ apply_filters( 'register_taxonomy_job_listing_category_args', array(
83
+ 'hierarchical' => true,
84
+ 'update_count_callback' => '_update_post_term_count',
85
+ 'label' => $plural,
86
+ 'labels' => array(
87
+ 'name' => $plural,
88
+ 'singular_name' => $singular,
89
+ 'menu_name' => ucwords( $plural ),
90
+ 'search_items' => sprintf( __( 'Search %s', 'wp-job-manager' ), $plural ),
91
+ 'all_items' => sprintf( __( 'All %s', 'wp-job-manager' ), $plural ),
92
+ 'parent_item' => sprintf( __( 'Parent %s', 'wp-job-manager' ), $singular ),
93
+ 'parent_item_colon' => sprintf( __( 'Parent %s:', 'wp-job-manager' ), $singular ),
94
+ 'edit_item' => sprintf( __( 'Edit %s', 'wp-job-manager' ), $singular ),
95
+ 'update_item' => sprintf( __( 'Update %s', 'wp-job-manager' ), $singular ),
96
+ 'add_new_item' => sprintf( __( 'Add New %s', 'wp-job-manager' ), $singular ),
97
+ 'new_item_name' => sprintf( __( 'New %s Name', 'wp-job-manager' ), $singular )
98
+ ),
99
+ 'show_ui' => true,
100
+ 'public' => $public,
101
+ 'capabilities' => array(
102
+ 'manage_terms' => $admin_capability,
103
+ 'edit_terms' => $admin_capability,
104
+ 'delete_terms' => $admin_capability,
105
+ 'assign_terms' => $admin_capability,
106
+ ),
107
+ 'rewrite' => $rewrite,
108
+ ) )
109
+ );
110
+ }
111
+
112
+ $singular = __( 'Job type', 'wp-job-manager' );
113
+ $plural = __( 'Job types', 'wp-job-manager' );
114
+
115
+ if ( current_theme_supports( 'job-manager-templates' ) ) {
116
+ $rewrite = array(
117
+ 'slug' => _x( 'job-type', 'Job type slug - resave permalinks after changing this', 'wp-job-manager' ),
118
+ 'with_front' => false,
119
+ 'hierarchical' => false
 
120
  );
121
+ $public = true;
122
+ } else {
123
+ $rewrite = false;
124
+ $public = false;
125
+ }
126
+
127
+ register_taxonomy( "job_listing_type",
128
+ apply_filters( 'register_taxonomy_job_listing_type_object_type', array( 'job_listing' ) ),
129
+ apply_filters( 'register_taxonomy_job_listing_type_args', array(
130
+ 'hierarchical' => true,
131
+ 'label' => $plural,
132
+ 'labels' => array(
133
+ 'name' => $plural,
134
+ 'singular_name' => $singular,
135
+ 'menu_name' => ucwords( $plural ),
136
+ 'search_items' => sprintf( __( 'Search %s', 'wp-job-manager' ), $plural ),
137
+ 'all_items' => sprintf( __( 'All %s', 'wp-job-manager' ), $plural ),
138
+ 'parent_item' => sprintf( __( 'Parent %s', 'wp-job-manager' ), $singular ),
139
+ 'parent_item_colon' => sprintf( __( 'Parent %s:', 'wp-job-manager' ), $singular ),
140
+ 'edit_item' => sprintf( __( 'Edit %s', 'wp-job-manager' ), $singular ),
141
+ 'update_item' => sprintf( __( 'Update %s', 'wp-job-manager' ), $singular ),
142
+ 'add_new_item' => sprintf( __( 'Add New %s', 'wp-job-manager' ), $singular ),
143
+ 'new_item_name' => sprintf( __( 'New %s Name', 'wp-job-manager' ), $singular )
144
+ ),
145
+ 'show_ui' => true,
146
+ 'public' => $public,
147
+ 'capabilities' => array(
148
+ 'manage_terms' => $admin_capability,
149
+ 'edit_terms' => $admin_capability,
150
+ 'delete_terms' => $admin_capability,
151
+ 'assign_terms' => $admin_capability,
152
+ ),
153
+ 'rewrite' => $rewrite,
154
+ ) )
155
+ );
156
+
157
+ /**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  * Post types
159
  */
160
+ $singular = __( 'Job', 'wp-job-manager' );
161
+ $plural = __( 'Jobs', 'wp-job-manager' );
162
 
163
+ if ( current_theme_supports( 'job-manager-templates' ) ) {
 
 
 
 
 
 
 
164
  $has_archive = _x( 'jobs', 'Post type archive slug - resave permalinks after changing this', 'wp-job-manager' );
165
  } else {
166
  $has_archive = false;
167
  }
168
 
169
+ $rewrite = array(
170
+ 'slug' => _x( 'job', 'Job permalink - resave permalinks after changing this', 'wp-job-manager' ),
171
  'with_front' => false,
172
  'feeds' => true,
173
+ 'pages' => false
174
  );
175
 
176
+ register_post_type( "job_listing",
177
+ apply_filters( "register_post_type_job_listing", array(
178
+ 'labels' => array(
179
+ 'name' => $plural,
180
+ 'singular_name' => $singular,
181
+ 'menu_name' => __( 'Job Listings', 'wp-job-manager' ),
182
+ 'all_items' => sprintf( __( 'All %s', 'wp-job-manager' ), $plural ),
183
+ 'add_new' => __( 'Add New', 'wp-job-manager' ),
184
+ 'add_new_item' => sprintf( __( 'Add %s', 'wp-job-manager' ), $singular ),
185
+ 'edit' => __( 'Edit', 'wp-job-manager' ),
186
+ 'edit_item' => sprintf( __( 'Edit %s', 'wp-job-manager' ), $singular ),
187
+ 'new_item' => sprintf( __( 'New %s', 'wp-job-manager' ), $singular ),
188
+ 'view' => sprintf( __( 'View %s', 'wp-job-manager' ), $singular ),
189
+ 'view_item' => sprintf( __( 'View %s', 'wp-job-manager' ), $singular ),
190
+ 'search_items' => sprintf( __( 'Search %s', 'wp-job-manager' ), $plural ),
191
+ 'not_found' => sprintf( __( 'No %s found', 'wp-job-manager' ), $plural ),
192
+ 'not_found_in_trash' => sprintf( __( 'No %s found in trash', 'wp-job-manager' ), $plural ),
193
+ 'parent' => sprintf( __( 'Parent %s', 'wp-job-manager' ), $singular ),
194
+ 'featured_image' => __( 'Company Logo', 'woocommerce' ),
195
+ 'set_featured_image' => __( 'Set company logo', 'woocommerce' ),
196
+ 'remove_featured_image' => __( 'Remove company logo', 'woocommerce' ),
197
+ 'use_featured_image' => __( 'Use as company logo', 'woocommerce' ),
198
+ ),
199
+ 'description' => sprintf( __( 'This is where you can create and manage %s.', 'wp-job-manager' ), $plural ),
200
+ 'public' => true,
201
+ 'show_ui' => true,
202
+ 'capability_type' => 'job_listing',
203
+ 'map_meta_cap' => true,
204
+ 'publicly_queryable' => true,
205
+ 'exclude_from_search' => false,
206
+ 'hierarchical' => false,
207
+ 'rewrite' => $rewrite,
208
+ 'query_var' => true,
209
+ 'supports' => array( 'title', 'editor', 'custom-fields', 'publicize', 'thumbnail' ),
210
+ 'has_archive' => $has_archive,
211
+ 'show_in_nav_menus' => false
212
+ ) )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  );
214
 
215
  /**
220
  /**
221
  * Post status
222
  */
223
+ register_post_status( 'expired', array(
224
+ 'label' => _x( 'Expired', 'post status', 'wp-job-manager' ),
225
+ 'public' => true,
226
+ 'protected' => true,
227
+ 'exclude_from_search' => true,
228
+ 'show_in_admin_all_list' => true,
229
+ 'show_in_admin_status_list' => true,
230
+ 'label_count' => _n_noop( 'Expired <span class="count">(%s)</span>', 'Expired <span class="count">(%s)</span>', 'wp-job-manager' ),
231
+ ) );
232
+ register_post_status( 'preview', array(
233
+ 'label' => _x( 'Preview', 'post status', 'wp-job-manager' ),
234
+ 'public' => false,
235
+ 'exclude_from_search' => true,
236
+ 'show_in_admin_all_list' => false,
237
+ 'show_in_admin_status_list' => true,
238
+ 'label_count' => _n_noop( 'Preview <span class="count">(%s)</span>', 'Preview <span class="count">(%s)</span>', 'wp-job-manager' ),
239
+ ) );
 
 
 
 
 
 
 
 
240
  }
241
 
242
  /**
243
+ * Change label
244
  */
245
  public function admin_head() {
246
  global $menu;
247
 
248
+ $plural = __( 'Job Listings', 'wp-job-manager' );
249
+ $count_jobs = wp_count_posts( 'job_listing', 'readable' );
 
 
 
 
250
 
251
+ if ( ! empty( $menu ) && is_array( $menu ) ) {
252
+ foreach ( $menu as $key => $menu_item ) {
253
+ if ( strpos( $menu_item[0], $plural ) === 0 ) {
254
+ if ( $order_count = $count_jobs->pending ) {
255
+ $menu[ $key ][0] .= " <span class='awaiting-mod update-plugins count-$order_count'><span class='pending-count'>" . number_format_i18n( $count_jobs->pending ) . "</span></span>" ;
256
+ }
257
+ break;
258
+ }
259
  }
260
  }
261
  }
262
 
263
  /**
264
+ * Toggle filter on and off
 
 
265
  */
266
  private function job_content_filter( $enable ) {
267
  if ( ! $enable ) {
272
  }
273
 
274
  /**
275
+ * Add extra content before/after the post for single job listings.
 
 
 
276
  */
277
  public function job_content( $content ) {
278
  global $post;
297
  }
298
 
299
  /**
300
+ * Job listing feeds
301
  */
302
  public function job_feed() {
 
 
303
  $query_args = array(
304
  'post_type' => 'job_listing',
305
  'post_status' => 'publish',
306
  'ignore_sticky_posts' => 1,
307
  'posts_per_page' => isset( $_GET['posts_per_page'] ) ? absint( $_GET['posts_per_page'] ) : 10,
 
308
  'tax_query' => array(),
309
+ 'meta_query' => array()
310
  );
311
 
312
  if ( ! empty( $_GET['search_location'] ) ) {
316
  $location_search[] = array(
317
  'key' => $meta_key,
318
  'value' => sanitize_text_field( $_GET['search_location'] ),
319
+ 'compare' => 'like'
320
  );
321
  }
322
  $query_args['meta_query'][] = $location_search;
326
  $query_args['tax_query'][] = array(
327
  'taxonomy' => 'job_listing_type',
328
  'field' => 'slug',
329
+ 'terms' => explode( ',', sanitize_text_field( $_GET['job_types'] ) ) + array( 0 )
330
  );
331
  }
332
 
333
  if ( ! empty( $_GET['job_categories'] ) ) {
334
+ $cats = explode( ',', sanitize_text_field( $_GET['job_categories'] ) ) + array( 0 );
335
+ $field = is_numeric( $cats ) ? 'term_id' : 'slug';
336
+ $operator = 'all' === get_option( 'job_manager_category_filter_type', 'all' ) && sizeof( $args['search_categories'] ) > 1 ? 'AND' : 'IN';
337
  $query_args['tax_query'][] = array(
338
  'taxonomy' => 'job_listing_category',
339
  'field' => $field,
340
  'terms' => $cats,
341
+ 'include_children' => $operator !== 'AND' ,
342
+ 'operator' => $operator
343
  );
344
  }
345
 
346
+ if ( $job_manager_keyword = sanitize_text_field( $_GET['search_keywords'] ) ) {
347
+ $query_args['_keyword'] = $job_manager_keyword; // Does nothing but needed for unique hash
348
+ add_filter( 'posts_clauses', 'get_job_listings_keyword_search' );
 
349
  }
350
 
351
  if ( empty( $query_args['meta_query'] ) ) {
356
  unset( $query_args['tax_query'] );
357
  }
358
 
359
+ query_posts( apply_filters( 'job_feed_args', $query_args ) );
360
  add_action( 'rss2_ns', array( $this, 'job_feed_namespace' ) );
361
  add_action( 'rss2_item', array( $this, 'job_feed_item' ) );
362
  do_feed_rss2( false );
 
363
  }
364
 
365
  /**
366
+ * Add a custom namespace to the job feed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  */
368
  public function job_feed_namespace() {
369
+ echo 'xmlns:job_listing="' . site_url() . '"' . "\n";
370
  }
371
 
372
  /**
373
+ * Add custom data to the job feed
374
  */
375
  public function job_feed_item() {
376
+ $post_id = get_the_ID();
377
+ $location = get_the_job_location( $post_id );
378
+ $job_type = get_the_job_type( $post_id );
379
+ $company = get_the_company_name( $post_id );
380
 
381
  if ( $location ) {
382
+ echo "<job_listing:location><![CDATA[" . esc_html( $location ) . "]]></job_listing:location>\n";
383
  }
384
+ if ( $job_type ) {
385
+ echo "<job_listing:job_type><![CDATA[" . esc_html( $job_type->name ) . "]]></job_listing:job_type>\n";
 
386
  }
387
  if ( $company ) {
388
+ echo "<job_listing:company><![CDATA[" . esc_html( $company ) . "]]></job_listing:company>\n";
389
  }
 
 
 
 
 
 
 
390
  }
391
 
392
  /**
393
+ * Expire jobs
394
  */
395
  public function check_for_expired_jobs() {
396
  global $wpdb;
397
 
398
+ // Change status to expired
399
+ $job_ids = $wpdb->get_col( $wpdb->prepare( "
400
+ SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
401
+ LEFT JOIN {$wpdb->posts} as posts ON postmeta.post_id = posts.ID
402
+ WHERE postmeta.meta_key = '_job_expires'
403
+ AND postmeta.meta_value > 0
404
+ AND postmeta.meta_value < %s
405
+ AND posts.post_status = 'publish'
406
+ AND posts.post_type = 'job_listing'
407
+ ", date( 'Y-m-d', current_time( 'timestamp' ) ) ) );
 
 
 
408
 
409
  if ( $job_ids ) {
410
  foreach ( $job_ids as $job_id ) {
411
+ $job_data = array();
412
+ $job_data['ID'] = $job_id;
413
  $job_data['post_status'] = 'expired';
414
  wp_update_post( $job_data );
415
  }
416
  }
417
 
418
+ // Delete old expired jobs
419
  if ( apply_filters( 'job_manager_delete_expired_jobs', false ) ) {
420
+ $job_ids = $wpdb->get_col( $wpdb->prepare( "
421
+ SELECT posts.ID FROM {$wpdb->posts} as posts
422
+ WHERE posts.post_type = 'job_listing'
423
+ AND posts.post_modified < %s
424
+ AND posts.post_status = 'expired'
425
+ ", date( 'Y-m-d', strtotime( '-' . apply_filters( 'job_manager_delete_expired_jobs_days', 30 ) . ' days', current_time( 'timestamp' ) ) ) ) );
 
 
 
426
 
427
  if ( $job_ids ) {
428
  foreach ( $job_ids as $job_id ) {
433
  }
434
 
435
  /**
436
+ * Delete old previewed jobs after 30 days to keep the DB clean
437
  */
438
  public function delete_old_previews() {
439
  global $wpdb;
440
 
441
+ // Delete old expired jobs
442
+ $job_ids = $wpdb->get_col( $wpdb->prepare( "
443
+ SELECT posts.ID FROM {$wpdb->posts} as posts
444
+ WHERE posts.post_type = 'job_listing'
445
+ AND posts.post_modified < %s
446
+ AND posts.post_status = 'preview'
447
+ ", date( 'Y-m-d', strtotime( '-30 days', current_time( 'timestamp' ) ) ) ) );
 
 
 
448
 
449
  if ( $job_ids ) {
450
  foreach ( $job_ids as $job_id ) {
454
  }
455
 
456
  /**
457
+ * Typo -.-
 
 
 
 
458
  */
459
  public function set_expirey( $post ) {
 
460
  $this->set_expiry( $post );
461
  }
462
 
463
  /**
464
+ * Set expirey date when job status changes
 
 
465
  */
466
  public function set_expiry( $post ) {
467
+ if ( $post->post_type !== 'job_listing' ) {
468
  return;
469
  }
470
 
471
+ // See if it is already set
472
  if ( metadata_exists( 'post', $post->ID, '_job_expires' ) ) {
473
  $expires = get_post_meta( $post->ID, '_job_expires', true );
474
  if ( $expires && strtotime( $expires ) < current_time( 'timestamp' ) ) {
475
  update_post_meta( $post->ID, '_job_expires', '' );
476
+ $_POST[ '_job_expires' ] = '';
477
  }
478
+ return;
479
  }
480
 
481
+ // No metadata set so we can generate an expiry date
482
+ // See if the user has set the expiry manually:
483
+ if ( ! empty( $_POST[ '_job_expires' ] ) ) {
484
+ update_post_meta( $post->ID, '_job_expires', date( 'Y-m-d', strtotime( sanitize_text_field( $_POST[ '_job_expires' ] ) ) ) );
485
+
486
+ // No manual setting? Lets generate a date
487
+ } else {
488
  $expires = calculate_job_expiry( $post->ID );
489
  update_post_meta( $post->ID, '_job_expires', $expires );
490
 
491
+ // In case we are saving a post, ensure post data is updated so the field is not overridden
492
+ if ( isset( $_POST[ '_job_expires' ] ) ) {
493
+ $_POST[ '_job_expires' ] = $expires;
494
  }
495
  }
496
  }
497
 
498
  /**
499
+ * The application content when the application method is an email
 
 
500
  */
501
  public function application_details_email( $apply ) {
502
  get_job_manager_template( 'job-application-email.php', array( 'apply' => $apply ) );
503
  }
504
 
505
  /**
506
+ * The application content when the application method is a url
 
 
507
  */
508
  public function application_details_url( $apply ) {
509
  get_job_manager_template( 'job-application-url.php', array( 'apply' => $apply ) );
510
  }
511
 
512
  /**
513
+ * Fix post name when wp_update_post changes it
514
+ * @param array $data
 
 
515
  * @return array
516
  */
517
  public function fix_post_name( $data, $postarr ) {
518
+ if ( 'job_listing' === $data['post_type'] && 'pending' === $data['post_status'] && ! current_user_can( 'publish_posts' ) ) {
519
+ $data['post_name'] = $postarr['post_name'];
520
+ }
521
+ return $data;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
522
  }
523
 
524
  /**
525
+ * Generate location data if a post is added
526
+ * @param int $post_id
527
+ * @param array $post
 
 
528
  */
529
  public function maybe_add_geolocation_data( $object_id, $meta_key, $meta_value ) {
530
  if ( '_job_location' !== $meta_key || 'job_listing' !== get_post_type( $object_id ) ) {
534
  }
535
 
536
  /**
537
+ * Triggered when updating meta on a job listing
 
 
 
 
 
538
  */
539
  public function update_post_meta( $meta_id, $object_id, $meta_key, $meta_value ) {
540
  if ( 'job_listing' === get_post_type( $object_id ) ) {
541
  switch ( $meta_key ) {
542
+ case '_job_location' :
543
  $this->maybe_update_geolocation_data( $meta_id, $object_id, $meta_key, $meta_value );
544
+ break;
545
+ case '_featured' :
546
  $this->maybe_update_menu_order( $meta_id, $object_id, $meta_key, $meta_value );
547
+ break;
548
  }
549
  }
550
  }
551
 
552
  /**
553
+ * Generate location data if a post is updated
 
 
 
 
 
554
  */
555
  public function maybe_update_geolocation_data( $meta_id, $object_id, $meta_key, $meta_value ) {
556
  do_action( 'job_manager_job_location_edited', $object_id, $meta_value );
557
  }
558
 
559
  /**
560
+ * Maybe set menu_order if the featured status of a job is changed
 
 
 
 
 
561
  */
562
  public function maybe_update_menu_order( $meta_id, $object_id, $meta_key, $meta_value ) {
563
  global $wpdb;
564
 
565
+ if ( '1' == $meta_value ) {
566
+ $wpdb->update( $wpdb->posts, array( 'menu_order' => -1 ), array( 'ID' => $object_id ) );
 
 
 
 
567
  } else {
568
+ $wpdb->update( $wpdb->posts, array( 'menu_order' => 0 ), array( 'ID' => $object_id, 'menu_order' => -1 ) );
 
 
 
 
 
 
 
569
  }
570
 
571
  clean_post_cache( $object_id );
572
  }
573
 
574
  /**
575
+ * Legacy
 
 
 
 
 
576
  * @deprecated 1.19.1
577
  */
578
  public function maybe_generate_geolocation_data( $meta_id, $object_id, $meta_key, $meta_value ) {
 
579
  $this->maybe_update_geolocation_data( $meta_id, $object_id, $meta_key, $meta_value );
580
  }
581
 
582
  /**
583
+ * Maybe set default meta data for job listings
584
+ * @param int $post_id
585
+ * @param WP_Post $post
 
586
  */
587
  public function maybe_add_default_meta_data( $post_id, $post = '' ) {
588
  if ( empty( $post ) || 'job_listing' === $post->post_type ) {
592
  }
593
 
594
  /**
595
+ * After importing via WP ALL Import, add default meta data
596
+ * @param int $post_id
597
  */
598
+ public function pmxi_saved_post( $post_id ) {
599
+ if ( 'job_listing' === get_post_type( $post_id ) ) {
600
+ $this->maybe_add_default_meta_data( $post_id );
601
+ if ( ! WP_Job_Manager_Geocode::has_location_data( $post_id ) && ( $location = get_post_meta( $post_id, '_job_location', true ) ) ) {
602
+ WP_Job_Manager_Geocode::generate_location_data( $post_id, $location );
603
+ }
 
 
 
 
 
 
604
  }
 
 
605
  }
606
 
607
  /**
608
+ * Replace RP4WP template with the template from Job Manager
609
+ * @param string $located
610
+ * @param string $template_name
611
+ * @param array $args
612
+ * @return string
613
  */
614
+ public function rp4wp_template( $located, $template_name, $args ) {
615
+ if ( 'related-post-default.php' === $template_name && 'job_listing' === $args['related_post']->post_type ) {
616
+ return JOB_MANAGER_PLUGIN_DIR . '/templates/content-job_listing.php';
617
  }
618
+ return $located;
619
+ }
620
 
621
+ /**
622
+ * Add meta fields for RP4WP to relate jobs by
623
+ * @param array $meta_fields
624
+ * @param int $post_id
625
+ * @param WP_Post $post
626
+ * @return array
627
+ */
628
+ public function rp4wp_related_meta_fields( $meta_fields, $post_id, $post ) {
629
+ if ( 'job_listing' === $post->post_type ) {
630
+ $meta_fields[] = '_company_name';
631
+ $meta_fields[] = '_job_location';
632
  }
633
+ return $meta_fields;
634
+ }
635
 
636
+ /**
637
+ * Add meta fields for RP4WP to relate jobs by
638
+ * @param int $weight
639
+ * @param WP_Post $post
640
+ * @param string $meta_field
641
+ * @return int
642
+ */
643
+ public function rp4wp_related_meta_fields_weight( $weight, $post, $meta_field ) {
644
+ if ( 'job_listing' === $post->post_type ) {
645
+ $weight = 100;
646
  }
647
+ return $weight;
648
  }
649
  }
includes/class-wp-job-manager-shortcodes.php CHANGED
@@ -1,49 +1,16 @@
1
  <?php
2
 
3
- if ( ! defined( 'ABSPATH' ) ) {
4
- exit; // Exit if accessed directly.
5
- }
6
 
7
  /**
8
- * Handles the shortcodes for WP Job Manager.
9
- *
10
- * @package wp-job-manager
11
- * @since 1.0.0
12
  */
13
  class WP_Job_Manager_Shortcodes {
14
 
15
- /**
16
- * Dashboard message.
17
- *
18
- * @access private
19
- * @var string
20
- */
21
  private $job_dashboard_message = '';
22
 
23
  /**
24
- * The single instance of the class.
25
- *
26
- * @var self
27
- * @since 1.26.0
28
- */
29
- private static $_instance = null;
30
-
31
- /**
32
- * Allows for accessing single instance of class. Class should only be constructed once per call.
33
- *
34
- * @since 1.26.0
35
- * @static
36
- * @return self Main instance.
37
- */
38
- public static function instance() {
39
- if ( is_null( self::$_instance ) ) {
40
- self::$_instance = new self();
41
- }
42
- return self::$_instance;
43
- }
44
-
45
- /**
46
- * Constructor.
47
  */
48
  public function __construct() {
49
  add_action( 'wp', array( $this, 'shortcode_action_handler' ) );
@@ -60,30 +27,25 @@ class WP_Job_Manager_Shortcodes {
60
  }
61
 
62
  /**
63
- * Handles actions which need to be run before the shortcode e.g. post actions.
64
  */
65
  public function shortcode_action_handler() {
66
  global $post;
67
 
68
- if ( is_page() && has_shortcode( $post->post_content, 'job_dashboard' ) ) {
69
  $this->job_dashboard_handler();
70
  }
71
  }
72
 
73
  /**
74
- * Shows the job submission form.
75
- *
76
- * @param array $atts
77
- * @return string|null
78
  */
79
  public function submit_job_form( $atts = array() ) {
80
  return $GLOBALS['job_manager']->forms->get_form( 'submit-job', $atts );
81
  }
82
 
83
  /**
84
- * Handles actions on job dashboard.
85
- *
86
- * @throws Exception On action handling error.
87
  */
88
  public function job_dashboard_handler() {
89
  if ( ! empty( $_REQUEST['action'] ) && ! empty( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( $_REQUEST['_wpnonce'], 'job_manager_my_job_actions' ) ) {
@@ -92,104 +54,66 @@ class WP_Job_Manager_Shortcodes {
92
  $job_id = absint( $_REQUEST['job_id'] );
93
 
94
  try {
95
- // Get Job.
96
- $job = get_post( $job_id );
97
 
98
- // Check ownership.
99
  if ( ! job_manager_user_can_edit_job( $job_id ) ) {
100
  throw new Exception( __( 'Invalid ID', 'wp-job-manager' ) );
101
  }
102
 
103
  switch ( $action ) {
104
- case 'mark_filled':
105
- // Check status.
106
- if ( 1 === intval( $job->_filled ) ) {
107
  throw new Exception( __( 'This position has already been filled', 'wp-job-manager' ) );
108
- }
109
 
110
- // Update.
111
  update_post_meta( $job_id, '_filled', 1 );
112
 
113
- // Message.
114
- // translators: Placeholder %s is the job listing title.
115
- $this->job_dashboard_message = '<div class="job-manager-message">' . esc_html( sprintf( __( '%s has been filled', 'wp-job-manager' ), wpjm_get_the_job_title( $job ) ) ) . '</div>';
116
  break;
117
- case 'mark_not_filled':
118
- // Check status.
119
- if ( 1 !== intval( $job->_filled ) ) {
120
  throw new Exception( __( 'This position is not filled', 'wp-job-manager' ) );
121
  }
122
 
123
- // Update.
124
  update_post_meta( $job_id, '_filled', 0 );
125
 
126
- // Message.
127
- // translators: Placeholder %s is the job listing title.
128
- $this->job_dashboard_message = '<div class="job-manager-message">' . esc_html( sprintf( __( '%s has been marked as not filled', 'wp-job-manager' ), wpjm_get_the_job_title( $job ) ) ) . '</div>';
129
  break;
130
- case 'delete':
131
- // Trash it.
132
  wp_trash_post( $job_id );
133
 
134
- // Message.
135
- // translators: Placeholder %s is the job listing title.
136
- $this->job_dashboard_message = '<div class="job-manager-message">' . esc_html( sprintf( __( '%s has been deleted', 'wp-job-manager' ), wpjm_get_the_job_title( $job ) ) ) . '</div>';
137
 
138
  break;
139
- case 'duplicate':
140
- if ( ! job_manager_get_permalink( 'submit_job_form' ) ) {
141
- throw new Exception( __( 'Missing submission page.', 'wp-job-manager' ) );
142
- }
143
-
144
- $new_job_id = job_manager_duplicate_listing( $job_id );
145
-
146
- if ( $new_job_id ) {
147
- wp_redirect( add_query_arg( array( 'job_id' => absint( $new_job_id ) ), job_manager_get_permalink( 'submit_job_form' ) ) );
148
- exit;
149
- }
150
 
151
  break;
152
- case 'relist':
153
- if ( ! job_manager_get_permalink( 'submit_job_form' ) ) {
154
- throw new Exception( __( 'Missing submission page.', 'wp-job-manager' ) );
155
- }
156
-
157
- // redirect to post page.
158
- wp_redirect( add_query_arg( array( 'job_id' => absint( $job_id ) ), job_manager_get_permalink( 'submit_job_form' ) ) );
159
- exit;
160
- default:
161
- do_action( 'job_manager_job_dashboard_do_action_' . $action, $job_id );
162
  break;
163
  }
164
 
165
  do_action( 'job_manager_my_job_do_action', $action, $job_id );
166
 
167
- /**
168
- * Set a success message for a custom dashboard action handler.
169
- *
170
- * When left empty, no success message will be shown.
171
- *
172
- * @since 1.31.1
173
- *
174
- * @param string $message Text for the success message. Default: empty string.
175
- * @param string $action The name of the custom action.
176
- * @param int $job_id The ID for the job that's been altered.
177
- */
178
- $success_message = apply_filters( 'job_manager_job_dashboard_success_message', '', $action, $job_id );
179
- if ( $success_message ) {
180
- $this->job_dashboard_message = '<div class="job-manager-message">' . $success_message . '</div>';
181
- }
182
  } catch ( Exception $e ) {
183
- $this->job_dashboard_message = '<div class="job-manager-error">' . wp_kses_post( $e->getMessage() ) . '</div>';
184
  }
185
  }
186
  }
187
 
188
  /**
189
- * Handles shortcode which lists the logged in user's jobs.
190
- *
191
- * @param array $atts
192
- * @return string
193
  */
194
  public function job_dashboard( $atts ) {
195
  if ( ! is_user_logged_in() ) {
@@ -198,12 +122,9 @@ class WP_Job_Manager_Shortcodes {
198
  return ob_get_clean();
199
  }
200
 
201
- $new_atts = shortcode_atts(
202
- array(
203
- 'posts_per_page' => '25',
204
- ), $atts
205
- );
206
- $posts_per_page = $new_atts['posts_per_page'];
207
 
208
  wp_enqueue_script( 'wp-job-manager-job-dashboard' );
209
 
@@ -213,7 +134,7 @@ class WP_Job_Manager_Shortcodes {
213
  if ( ! empty( $_REQUEST['action'] ) ) {
214
  $action = sanitize_title( $_REQUEST['action'] );
215
 
216
- // Show alternative content if a plugin wants to.
217
  if ( has_action( 'job_manager_job_dashboard_content_' . $action ) ) {
218
  do_action( 'job_manager_job_dashboard_content_' . $action, $atts );
219
 
@@ -221,221 +142,183 @@ class WP_Job_Manager_Shortcodes {
221
  }
222
  }
223
 
224
- // ....If not show the job dashboard.
225
- $args = apply_filters(
226
- 'job_manager_get_dashboard_jobs_args',
227
- array(
228
- 'post_type' => 'job_listing',
229
- 'post_status' => array( 'publish', 'expired', 'pending' ),
230
- 'ignore_sticky_posts' => 1,
231
- 'posts_per_page' => $posts_per_page,
232
- 'offset' => ( max( 1, get_query_var( 'paged' ) ) - 1 ) * $posts_per_page,
233
- 'orderby' => 'date',
234
- 'order' => 'desc',
235
- 'author' => get_current_user_id(),
236
- )
237
- );
238
 
239
- $jobs = new WP_Query();
240
 
241
- echo wp_kses_post( $this->job_dashboard_message );
242
 
243
- $job_dashboard_columns = apply_filters(
244
- 'job_manager_job_dashboard_columns',
245
- array(
246
- 'job_title' => __( 'Title', 'wp-job-manager' ),
247
- 'filled' => __( 'Filled?', 'wp-job-manager' ),
248
- 'date' => __( 'Date Posted', 'wp-job-manager' ),
249
- 'expires' => __( 'Listing Expires', 'wp-job-manager' ),
250
- )
251
- );
252
 
253
- get_job_manager_template(
254
- 'job-dashboard.php',
255
- array(
256
- 'jobs' => $jobs->query( $args ),
257
- 'max_num_pages' => $jobs->max_num_pages,
258
- 'job_dashboard_columns' => $job_dashboard_columns,
259
- )
260
- );
261
 
262
  return ob_get_clean();
263
  }
264
 
265
  /**
266
- * Displays edit job form.
267
  */
268
  public function edit_job() {
269
  global $job_manager;
270
 
271
- echo $job_manager->forms->get_form( 'edit-job' ); // WPCS: XSS ok.
272
  }
273
 
274
  /**
275
- * Lists all job listings.
276
  *
277
- * @param array $atts
278
- * @return string
 
279
  */
280
  public function output_jobs( $atts ) {
281
  ob_start();
282
 
283
- $atts = shortcode_atts(
284
- apply_filters(
285
- 'job_manager_output_jobs_defaults',
286
- array(
287
- 'per_page' => get_option( 'job_manager_per_page' ),
288
- 'orderby' => 'featured',
289
- 'order' => 'DESC',
290
-
291
- // Filters + cats.
292
- 'show_filters' => true,
293
- 'show_categories' => true,
294
- 'show_category_multiselect' => get_option( 'job_manager_enable_default_category_multiselect', false ),
295
- 'show_pagination' => false,
296
- 'show_more' => true,
297
-
298
- // Limit what jobs are shown based on category, post status, and type.
299
- 'categories' => '',
300
- 'job_types' => '',
301
- 'post_status' => '',
302
- 'featured' => null, // True to show only featured, false to hide featured, leave null to show both.
303
- 'filled' => null, // True to show only filled, false to hide filled, leave null to show both/use the settings.
304
-
305
- // Default values for filters.
306
- 'location' => '',
307
- 'keywords' => '',
308
- 'selected_category' => '',
309
- 'selected_job_types' => implode( ',', array_values( get_job_listing_types( 'id=>slug' ) ) ),
310
- )
311
- ), $atts
312
- );
313
 
314
  if ( ! get_option( 'job_manager_enable_categories' ) ) {
315
- $atts['show_categories'] = false;
316
  }
317
 
318
- // String and bool handling.
319
- $atts['show_filters'] = $this->string_to_bool( $atts['show_filters'] );
320
- $atts['show_categories'] = $this->string_to_bool( $atts['show_categories'] );
321
- $atts['show_category_multiselect'] = $this->string_to_bool( $atts['show_category_multiselect'] );
322
- $atts['show_more'] = $this->string_to_bool( $atts['show_more'] );
323
- $atts['show_pagination'] = $this->string_to_bool( $atts['show_pagination'] );
324
 
325
- if ( ! is_null( $atts['featured'] ) ) {
326
- $atts['featured'] = ( is_bool( $atts['featured'] ) && $atts['featured'] ) || in_array( $atts['featured'], array( 1, '1', 'true', 'yes' ), true );
327
  }
328
 
329
- if ( ! is_null( $atts['filled'] ) ) {
330
- $atts['filled'] = ( is_bool( $atts['filled'] ) && $atts['filled'] ) || in_array( $atts['filled'], array( 1, '1', 'true', 'yes' ), true );
331
  }
332
 
333
- // Array handling.
334
- $atts['categories'] = is_array( $atts['categories'] ) ? $atts['categories'] : array_filter( array_map( 'trim', explode( ',', $atts['categories'] ) ) );
335
- $atts['job_types'] = is_array( $atts['job_types'] ) ? $atts['job_types'] : array_filter( array_map( 'trim', explode( ',', $atts['job_types'] ) ) );
336
- $atts['post_status'] = is_array( $atts['post_status'] ) ? $atts['post_status'] : array_filter( array_map( 'trim', explode( ',', $atts['post_status'] ) ) );
337
- $atts['selected_job_types'] = is_array( $atts['selected_job_types'] ) ? $atts['selected_job_types'] : array_filter( array_map( 'trim', explode( ',', $atts['selected_job_types'] ) ) );
338
 
339
- // Get keywords and location from querystring if set.
340
  if ( ! empty( $_GET['search_keywords'] ) ) {
341
- $atts['keywords'] = sanitize_text_field( $_GET['search_keywords'] );
342
  }
343
  if ( ! empty( $_GET['search_location'] ) ) {
344
- $atts['location'] = sanitize_text_field( $_GET['search_location'] );
345
  }
346
  if ( ! empty( $_GET['search_category'] ) ) {
347
- $atts['selected_category'] = sanitize_text_field( $_GET['search_category'] );
348
  }
349
 
350
- $data_attributes = array(
351
- 'location' => $atts['location'],
352
- 'keywords' => $atts['keywords'],
353
- 'show_filters' => $atts['show_filters'] ? 'true' : 'false',
354
- 'show_pagination' => $atts['show_pagination'] ? 'true' : 'false',
355
- 'per_page' => $atts['per_page'],
356
- 'orderby' => $atts['orderby'],
357
- 'order' => $atts['order'],
358
- 'categories' => implode( ',', $atts['categories'] ),
359
- );
360
- if ( $atts['show_filters'] ) {
361
-
362
- get_job_manager_template(
363
- 'job-filters.php',
364
- array(
365
- 'per_page' => $atts['per_page'],
366
- 'orderby' => $atts['orderby'],
367
- 'order' => $atts['order'],
368
- 'show_categories' => $atts['show_categories'],
369
- 'categories' => $atts['categories'],
370
- 'selected_category' => $atts['selected_category'],
371
- 'job_types' => $atts['job_types'],
372
- 'atts' => $atts,
373
- 'location' => $atts['location'],
374
- 'keywords' => $atts['keywords'],
375
- 'selected_job_types' => $atts['selected_job_types'],
376
- 'show_category_multiselect' => $atts['show_category_multiselect'],
377
- )
378
- );
379
 
380
  get_job_manager_template( 'job-listings-start.php' );
381
  get_job_manager_template( 'job-listings-end.php' );
382
 
383
- if ( ! $atts['show_pagination'] && $atts['show_more'] ) {
384
- echo '<a class="load_more_jobs" href="#" style="display:none;"><strong>' . esc_html__( 'Load more listings', 'wp-job-manager' ) . '</strong></a>';
385
  }
 
386
  } else {
387
- $jobs = get_job_listings(
388
- apply_filters(
389
- 'job_manager_output_jobs_args',
390
- array(
391
- 'search_location' => $atts['location'],
392
- 'search_keywords' => $atts['keywords'],
393
- 'post_status' => $atts['post_status'],
394
- 'search_categories' => $atts['categories'],
395
- 'job_types' => $atts['job_types'],
396
- 'orderby' => $atts['orderby'],
397
- 'order' => $atts['order'],
398
- 'posts_per_page' => $atts['per_page'],
399
- 'featured' => $atts['featured'],
400
- 'filled' => $atts['filled'],
401
- )
402
- )
403
- );
404
-
405
- if ( ! empty( $atts['job_types'] ) ) {
406
- $data_attributes['job_types'] = implode( ',', $atts['job_types'] );
407
- }
408
 
409
- if ( $jobs->have_posts() ) {
410
- get_job_manager_template( 'job-listings-start.php' );
411
- while ( $jobs->have_posts() ) {
412
- $jobs->the_post();
413
- get_job_manager_template_part( 'content', 'job_listing' );
414
- }
415
- get_job_manager_template( 'job-listings-end.php' );
416
- if ( $jobs->found_posts > $atts['per_page'] && $atts['show_more'] ) {
417
- wp_enqueue_script( 'wp-job-manager-ajax-filters' );
418
- if ( $atts['show_pagination'] ) {
419
- echo get_job_listing_pagination( $jobs->max_num_pages ); // WPCS: XSS ok.
420
- } else {
421
- echo '<a class="load_more_jobs" href="#"><strong>' . esc_html__( 'Load more listings', 'wp-job-manager' ) . '</strong></a>';
422
- }
423
- }
424
- } else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  do_action( 'job_manager_output_jobs_no_results' );
426
- }
 
427
  wp_reset_postdata();
428
  }
429
 
430
  $data_attributes_string = '';
431
- if ( ! is_null( $atts['featured'] ) ) {
432
- $data_attributes['featured'] = $atts['featured'] ? 'true' : 'false';
433
- }
434
- if ( ! is_null( $atts['filled'] ) ) {
435
- $data_attributes['filled'] = $atts['filled'] ? 'true' : 'false';
 
 
 
 
 
 
 
436
  }
437
- if ( ! empty( $atts['post_status'] ) ) {
438
- $data_attributes['post_status'] = implode( ',', $atts['post_status'] );
439
  }
440
  foreach ( $data_attributes as $key => $value ) {
441
  $data_attributes_string .= 'data-' . esc_attr( $key ) . '="' . esc_attr( $value ) . '" ';
@@ -447,82 +330,77 @@ class WP_Job_Manager_Shortcodes {
447
  }
448
 
449
  /**
450
- * Displays some content when no results were found.
451
  */
452
  public function output_no_results() {
453
  get_job_manager_template( 'content-no-jobs-found.php' );
454
  }
455
 
456
  /**
457
- * Gets string as a bool.
458
- *
459
  * @param string $value
460
  * @return bool
461
  */
462
  public function string_to_bool( $value ) {
463
- return ( is_bool( $value ) && $value ) || in_array( $value, array( 1, '1', 'true', 'yes' ), true );
464
  }
465
 
466
  /**
467
- * Shows job types.
468
- *
469
  * @param array $atts
470
  */
471
  public function job_filter_job_types( $atts ) {
472
- $job_types = is_array( $atts['job_types'] ) ? $atts['job_types'] : array_filter( array_map( 'trim', explode( ',', $atts['job_types'] ) ) );
473
- $selected_job_types = is_array( $atts['selected_job_types'] ) ? $atts['selected_job_types'] : array_filter( array_map( 'trim', explode( ',', $atts['selected_job_types'] ) ) );
474
-
475
- get_job_manager_template(
476
- 'job-filter-job-types.php',
477
- array(
478
- 'job_types' => $job_types,
479
- 'atts' => $atts,
480
- 'selected_job_types' => $selected_job_types,
481
- )
482
- );
483
  }
484
 
485
  /**
486
- * Shows results div.
487
  */
488
  public function job_filter_results() {
489
  echo '<div class="showing_jobs"></div>';
490
  }
491
 
492
  /**
493
- * Shows a single job.
494
  *
495
- * @param array $atts
496
- * @return string|null
 
497
  */
498
  public function output_job( $atts ) {
499
- $atts = shortcode_atts(
500
- array(
501
- 'id' => '',
502
- ), $atts
503
- );
504
 
505
- if ( ! $atts['id'] ) {
506
  return;
507
- }
508
 
509
  ob_start();
510
 
511
  $args = array(
512
  'post_type' => 'job_listing',
513
  'post_status' => 'publish',
514
- 'p' => $atts['id'],
515
  );
516
 
517
  $jobs = new WP_Query( $args );
518
 
519
- if ( $jobs->have_posts() ) {
520
- while ( $jobs->have_posts() ) {
521
- $jobs->the_post();
522
- echo '<h1>' . esc_html( wpjm_get_the_job_title() ) . '</h1>';
523
- get_job_manager_template_part( 'content-single', 'job_listing' );
524
- }
525
- }
 
 
 
 
526
 
527
  wp_reset_postdata();
528
 
@@ -530,56 +408,57 @@ class WP_Job_Manager_Shortcodes {
530
  }
531
 
532
  /**
533
- * Handles the Job Summary shortcode.
534
  *
535
- * @param array $atts
 
536
  * @return string
537
  */
538
  public function output_job_summary( $atts ) {
539
- $atts = shortcode_atts(
540
- array(
541
- 'id' => '',
542
- 'width' => '250px',
543
- 'align' => 'left',
544
- 'featured' => null, // True to show only featured, false to hide featured, leave null to show both (when leaving out id).
545
- 'limit' => 1,
546
- ), $atts
547
- );
548
 
549
  ob_start();
550
 
551
  $args = array(
552
  'post_type' => 'job_listing',
553
- 'post_status' => 'publish',
554
  );
555
 
556
- if ( ! $atts['id'] ) {
557
- $args['posts_per_page'] = $atts['limit'];
558
  $args['orderby'] = 'rand';
559
- if ( ! is_null( $atts['featured'] ) ) {
560
- $args['meta_query'] = array(
561
- array(
562
- 'key' => '_featured',
563
- 'value' => '1',
564
- 'compare' => $atts['featured'] ? '=' : '!=',
565
- ),
566
- );
567
  }
568
  } else {
569
- $args['p'] = absint( $atts['id'] );
570
  }
571
 
572
  $jobs = new WP_Query( $args );
573
 
574
- if ( $jobs->have_posts() ) {
575
- while ( $jobs->have_posts() ) {
576
- $jobs->the_post();
577
- $width = $atts['width'] ? $atts['width'] : 'auto';
578
- echo '<div class="job_summary_shortcode align' . esc_attr( $atts['align'] ) . '" style="width: ' . esc_attr( $width ) . '">';
579
- get_job_manager_template_part( 'content-summary', 'job_listing' );
580
- echo '</div>';
581
- }
582
- }
 
 
 
 
583
 
584
  wp_reset_postdata();
585
 
@@ -587,24 +466,18 @@ class WP_Job_Manager_Shortcodes {
587
  }
588
 
589
  /**
590
- * Shows the application area.
591
- *
592
- * @param array $atts
593
- * @return string
594
  */
595
  public function output_job_apply( $atts ) {
596
- $new_atts = shortcode_atts(
597
- array(
598
- 'id' => '',
599
- ), $atts
600
- );
601
- $id = $new_atts['id'];
602
 
603
  ob_start();
604
 
605
  $args = array(
606
  'post_type' => 'job_listing',
607
- 'post_status' => 'publish',
608
  );
609
 
610
  if ( ! $id ) {
@@ -615,23 +488,31 @@ class WP_Job_Manager_Shortcodes {
615
 
616
  $jobs = new WP_Query( $args );
617
 
618
- if ( $jobs->have_posts() ) {
619
- while ( $jobs->have_posts() ) {
 
620
  $jobs->the_post();
621
  $apply = get_the_job_application_method();
622
- do_action( 'job_manager_before_job_apply_' . absint( $id ) );
623
- if ( apply_filters( 'job_manager_show_job_apply_' . absint( $id ), true ) ) {
624
- echo '<div class="job-manager-application-wrapper">';
625
- do_action( 'job_manager_application_details_' . $apply->type, $apply );
626
- echo '</div>';
627
- }
628
- do_action( 'job_manager_after_job_apply_' . absint( $id ) );
629
- }
630
- wp_reset_postdata();
631
- }
 
 
 
 
 
 
 
632
 
633
  return ob_get_clean();
634
  }
635
  }
636
 
637
- WP_Job_Manager_Shortcodes::instance();
1
  <?php
2
 
3
+ if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
 
 
4
 
5
  /**
6
+ * WP_Job_Manager_Shortcodes class.
 
 
 
7
  */
8
  class WP_Job_Manager_Shortcodes {
9
 
 
 
 
 
 
 
10
  private $job_dashboard_message = '';
11
 
12
  /**
13
+ * Constructor
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  */
15
  public function __construct() {
16
  add_action( 'wp', array( $this, 'shortcode_action_handler' ) );
27
  }
28
 
29
  /**
30
+ * Handle actions which need to be run before the shortcode e.g. post actions
31
  */
32
  public function shortcode_action_handler() {
33
  global $post;
34
 
35
+ if ( is_page() && strstr( $post->post_content, '[job_dashboard' ) ) {
36
  $this->job_dashboard_handler();
37
  }
38
  }
39
 
40
  /**
41
+ * Show the job submission form
 
 
 
42
  */
43
  public function submit_job_form( $atts = array() ) {
44
  return $GLOBALS['job_manager']->forms->get_form( 'submit-job', $atts );
45
  }
46
 
47
  /**
48
+ * Handles actions on job dashboard
 
 
49
  */
50
  public function job_dashboard_handler() {
51
  if ( ! empty( $_REQUEST['action'] ) && ! empty( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( $_REQUEST['_wpnonce'], 'job_manager_my_job_actions' ) ) {
54
  $job_id = absint( $_REQUEST['job_id'] );
55
 
56
  try {
57
+ // Get Job
58
+ $job = get_post( $job_id );
59
 
60
+ // Check ownership
61
  if ( ! job_manager_user_can_edit_job( $job_id ) ) {
62
  throw new Exception( __( 'Invalid ID', 'wp-job-manager' ) );
63
  }
64
 
65
  switch ( $action ) {
66
+ case 'mark_filled' :
67
+ // Check status
68
+ if ( $job->_filled == 1 )
69
  throw new Exception( __( 'This position has already been filled', 'wp-job-manager' ) );
 
70
 
71
+ // Update
72
  update_post_meta( $job_id, '_filled', 1 );
73
 
74
+ // Message
75
+ $this->job_dashboard_message = '<div class="job-manager-message">' . sprintf( __( '%s has been filled', 'wp-job-manager' ), $job->post_title ) . '</div>';
 
76
  break;
77
+ case 'mark_not_filled' :
78
+ // Check status
79
+ if ( $job->_filled != 1 ) {
80
  throw new Exception( __( 'This position is not filled', 'wp-job-manager' ) );
81
  }
82
 
83
+ // Update
84
  update_post_meta( $job_id, '_filled', 0 );
85
 
86
+ // Message
87
+ $this->job_dashboard_message = '<div class="job-manager-message">' . sprintf( __( '%s has been marked as not filled', 'wp-job-manager' ), $job->post_title ) . '</div>';
 
88
  break;
89
+ case 'delete' :
90
+ // Trash it
91
  wp_trash_post( $job_id );
92
 
93
+ // Message
94
+ $this->job_dashboard_message = '<div class="job-manager-message">' . sprintf( __( '%s has been deleted', 'wp-job-manager' ), $job->post_title ) . '</div>';
 
95
 
96
  break;
97
+ case 'relist' :
98
+ // redirect to post page
99
+ wp_redirect( add_query_arg( array( 'job_id' => absint( $job_id ) ), job_manager_get_permalink( 'submit_job_form' ) ) );
 
 
 
 
 
 
 
 
100
 
101
  break;
102
+ default :
103
+ do_action( 'job_manager_job_dashboard_do_action_' . $action );
 
 
 
 
 
 
 
 
104
  break;
105
  }
106
 
107
  do_action( 'job_manager_my_job_do_action', $action, $job_id );
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  } catch ( Exception $e ) {
110
+ $this->job_dashboard_message = '<div class="job-manager-error">' . $e->getMessage() . '</div>';
111
  }
112
  }
113
  }
114
 
115
  /**
116
+ * Shortcode which lists the logged in user's jobs
 
 
 
117
  */
118
  public function job_dashboard( $atts ) {
119
  if ( ! is_user_logged_in() ) {
122
  return ob_get_clean();
123
  }
124
 
125
+ extract( shortcode_atts( array(
126
+ 'posts_per_page' => '25',
127
+ ), $atts ) );
 
 
 
128
 
129
  wp_enqueue_script( 'wp-job-manager-job-dashboard' );
130
 
134
  if ( ! empty( $_REQUEST['action'] ) ) {
135
  $action = sanitize_title( $_REQUEST['action'] );
136
 
137
+ // Show alternative content if a plugin wants to
138
  if ( has_action( 'job_manager_job_dashboard_content_' . $action ) ) {
139
  do_action( 'job_manager_job_dashboard_content_' . $action, $atts );
140
 
142
  }
143
  }
144
 
145
+ // ....If not show the job dashboard
146
+ $args = apply_filters( 'job_manager_get_dashboard_jobs_args', array(
147
+ 'post_type' => 'job_listing',
148
+ 'post_status' => array( 'publish', 'expired', 'pending' ),
149
+ 'ignore_sticky_posts' => 1,
150
+ 'posts_per_page' => $posts_per_page,
151
+ 'offset' => ( max( 1, get_query_var('paged') ) - 1 ) * $posts_per_page,
152
+ 'orderby' => 'date',
153
+ 'order' => 'desc',
154
+ 'author' => get_current_user_id()
155
+ ) );
 
 
 
156
 
157
+ $jobs = new WP_Query;
158
 
159
+ echo $this->job_dashboard_message;
160
 
161
+ $job_dashboard_columns = apply_filters( 'job_manager_job_dashboard_columns', array(
162
+ 'job_title' => __( 'Title', 'wp-job-manager' ),
163
+ 'filled' => __( 'Filled?', 'wp-job-manager' ),
164
+ 'date' => __( 'Date Posted', 'wp-job-manager' ),
165
+ 'expires' => __( 'Listing Expires', 'wp-job-manager' )
166
+ ) );
 
 
 
167
 
168
+ get_job_manager_template( 'job-dashboard.php', array( 'jobs' => $jobs->query( $args ), 'max_num_pages' => $jobs->max_num_pages, 'job_dashboard_columns' => $job_dashboard_columns ) );
 
 
 
 
 
 
 
169
 
170
  return ob_get_clean();
171
  }
172
 
173
  /**
174
+ * Edit job form
175
  */
176
  public function edit_job() {
177
  global $job_manager;
178
 
179
+ echo $job_manager->forms->get_form( 'edit-job' );
180
  }
181
 
182
  /**
183
+ * output_jobs function.
184
  *
185
+ * @access public
186
+ * @param mixed $args
187
+ * @return void
188
  */
189
  public function output_jobs( $atts ) {
190
  ob_start();
191
 
192
+ extract( $atts = shortcode_atts( apply_filters( 'job_manager_output_jobs_defaults', array(
193
+ 'per_page' => get_option( 'job_manager_per_page' ),
194
+ 'orderby' => 'featured',
195
+ 'order' => 'DESC',
196
+
197
+ // Filters + cats
198
+ 'show_filters' => true,
199
+ 'show_categories' => true,
200
+ 'show_category_multiselect' => get_option( 'job_manager_enable_default_category_multiselect', false ),
201
+ 'show_pagination' => false,
202
+ 'show_more' => true,
203
+
204
+ // Limit what jobs are shown based on category and type
205
+ 'categories' => '',
206
+ 'job_types' => '',
207
+ 'featured' => null, // True to show only featured, false to hide featured, leave null to show both.
208
+ 'filled' => null, // True to show only filled, false to hide filled, leave null to show both/use the settings.
209
+
210
+ // Default values for filters
211
+ 'location' => '',
212
+ 'keywords' => '',
213
+ 'selected_category' => '',
214
+ 'selected_job_types' => implode( ',', array_values( get_job_listing_types( 'id=>slug' ) ) ),
215
+ ) ), $atts ) );
 
 
 
 
 
 
216
 
217
  if ( ! get_option( 'job_manager_enable_categories' ) ) {
218
+ $show_categories = false;
219
  }
220
 
221
+ // String and bool handling
222
+ $show_filters = $this->string_to_bool( $show_filters );
223
+ $show_categories = $this->string_to_bool( $show_categories );
224
+ $show_category_multiselect = $this->string_to_bool( $show_category_multiselect );
225
+ $show_more = $this->string_to_bool( $show_more );
226
+ $show_pagination = $this->string_to_bool( $show_pagination );
227
 
228
+ if ( ! is_null( $featured ) ) {
229
+ $featured = ( is_bool( $featured ) && $featured ) || in_array( $featured, array( '1', 'true', 'yes' ) ) ? true : false;
230
  }
231
 
232
+ if ( ! is_null( $filled ) ) {
233
+ $filled = ( is_bool( $filled ) && $filled ) || in_array( $filled, array( '1', 'true', 'yes' ) ) ? true : false;
234
  }
235
 
236
+ // Array handling
237
+ $categories = is_array( $categories ) ? $categories : array_filter( array_map( 'trim', explode( ',', $categories ) ) );
238
+ $job_types = is_array( $job_types ) ? $job_types : array_filter( array_map( 'trim', explode( ',', $job_types ) ) );
239
+ $selected_job_types = is_array( $selected_job_types ) ? $selected_job_types : array_filter( array_map( 'trim', explode( ',', $selected_job_types ) ) );
 
240
 
241
+ // Get keywords and location from querystring if set
242
  if ( ! empty( $_GET['search_keywords'] ) ) {
243
+ $keywords = sanitize_text_field( $_GET['search_keywords'] );
244
  }
245
  if ( ! empty( $_GET['search_location'] ) ) {
246
+ $location = sanitize_text_field( $_GET['search_location'] );
247
  }
248
  if ( ! empty( $_GET['search_category'] ) ) {
249
+ $selected_category = sanitize_text_field( $_GET['search_category'] );
250
  }
251
 
252
+ if ( $show_filters ) {
253
+
254
+ get_job_manager_template( 'job-filters.php', array( 'per_page' => $per_page, 'orderby' => $orderby, 'order' => $order, 'show_categories' => $show_categories, 'categories' => $categories, 'selected_category' => $selected_category, 'job_types' => $job_types, 'atts' => $atts, 'location' => $location, 'keywords' => $keywords, 'selected_job_types' => $selected_job_types, 'show_category_multiselect' => $show_category_multiselect ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
  get_job_manager_template( 'job-listings-start.php' );
257
  get_job_manager_template( 'job-listings-end.php' );
258
 
259
+ if ( ! $show_pagination && $show_more ) {
260
+ echo '<a class="load_more_jobs" href="#" style="display:none;"><strong>' . __( 'Load more listings', 'wp-job-manager' ) . '</strong></a>';
261
  }
262
+
263
  } else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
 
265
+ $jobs = get_job_listings( apply_filters( 'job_manager_output_jobs_args', array(
266
+ 'search_location' => $location,
267
+ 'search_keywords' => $keywords,
268
+ 'search_categories' => $categories,
269
+ 'job_types' => $job_types,
270
+ 'orderby' => $orderby,
271
+ 'order' => $order,
272
+ 'posts_per_page' => $per_page,
273
+ 'featured' => $featured,
274
+ 'filled' => $filled
275
+ ) ) );
276
+
277
+ if ( $jobs->have_posts() ) : ?>
278
+
279
+ <?php get_job_manager_template( 'job-listings-start.php' ); ?>
280
+
281
+ <?php while ( $jobs->have_posts() ) : $jobs->the_post(); ?>
282
+ <?php get_job_manager_template_part( 'content', 'job_listing' ); ?>
283
+ <?php endwhile; ?>
284
+
285
+ <?php get_job_manager_template( 'job-listings-end.php' ); ?>
286
+
287
+ <?php if ( $jobs->found_posts > $per_page && $show_more ) : ?>
288
+
289
+ <?php wp_enqueue_script( 'wp-job-manager-ajax-filters' ); ?>
290
+
291
+ <?php if ( $show_pagination ) : ?>
292
+ <?php echo get_job_listing_pagination( $jobs->max_num_pages ); ?>
293
+ <?php else : ?>
294
+ <a class="load_more_jobs" href="#"><strong><?php _e( 'Load more listings', 'wp-job-manager' ); ?></strong></a>
295
+ <?php endif; ?>
296
+
297
+ <?php endif; ?>
298
+
299
+ <?php else :
300
  do_action( 'job_manager_output_jobs_no_results' );
301
+ endif;
302
+
303
  wp_reset_postdata();
304
  }
305
 
306
  $data_attributes_string = '';
307
+ $data_attributes = array(
308
+ 'location' => $location,
309
+ 'keywords' => $keywords,
310
+ 'show_filters' => $show_filters ? 'true' : 'false',
311
+ 'show_pagination' => $show_pagination ? 'true' : 'false',
312
+ 'per_page' => $per_page,
313
+ 'orderby' => $orderby,
314
+ 'order' => $order,
315
+ 'categories' => implode( ',', $categories ),
316
+ );
317
+ if ( ! is_null( $featured ) ) {
318
+ $data_attributes[ 'featured' ] = $featured ? 'true' : 'false';
319
  }
320
+ if ( ! is_null( $filled ) ) {
321
+ $data_attributes[ 'filled' ] = $filled ? 'true' : 'false';
322
  }
323
  foreach ( $data_attributes as $key => $value ) {
324
  $data_attributes_string .= 'data-' . esc_attr( $key ) . '="' . esc_attr( $value ) . '" ';
330
  }
331
 
332
  /**
333
+ * Output some content when no results were found
334
  */
335
  public function output_no_results() {
336
  get_job_manager_template( 'content-no-jobs-found.php' );
337
  }
338
 
339
  /**
340
+ * Get string as a bool
 
341
  * @param string $value
342
  * @return bool
343
  */
344
  public function string_to_bool( $value ) {
345
+ return ( is_bool( $value ) && $value ) || in_array( $value, array( '1', 'true', 'yes' ) ) ? true : false;
346
  }
347
 
348
  /**
349
+ * Show job types
 
350
  * @param array $atts
351
  */
352
  public function job_filter_job_types( $atts ) {
353
+ extract( $atts );
354
+
355
+ $job_types = array_filter( array_map( 'trim', explode( ',', $job_types ) ) );
356
+ $selected_job_types = array_filter( array_map( 'trim', explode( ',', $selected_job_types ) ) );
357
+
358
+ get_job_manager_template( 'job-filter-job-types.php', array( 'job_types' => $job_types, 'atts' => $atts, 'selected_job_types' => $selected_job_types ) );
 
 
 
 
 
359
  }
360
 
361
  /**
362
+ * Show results div
363
  */
364
  public function job_filter_results() {
365
  echo '<div class="showing_jobs"></div>';
366
  }
367
 
368
  /**
369
+ * output_job function.
370
  *
371
+ * @access public
372
+ * @param array $args
373
+ * @return string
374
  */
375
  public function output_job( $atts ) {
376
+ extract( shortcode_atts( array(
377
+ 'id' => '',
378
+ ), $atts ) );
 
 
379
 
380
+ if ( ! $id )
381
  return;
 
382
 
383
  ob_start();
384
 
385
  $args = array(
386
  'post_type' => 'job_listing',
387
  'post_status' => 'publish',
388
+ 'p' => $id
389
  );
390
 
391
  $jobs = new WP_Query( $args );
392
 
393
+ if ( $jobs->have_posts() ) : ?>
394
+
395
+ <?php while ( $jobs->have_posts() ) : $jobs->the_post(); ?>
396
+
397
+ <h1><?php the_title(); ?></h1>
398
+
399
+ <?php get_job_manager_template_part( 'content-single', 'job_listing' ); ?>
400
+
401
+ <?php endwhile; ?>
402
+
403
+ <?php endif;
404
 
405
  wp_reset_postdata();
406
 
408
  }
409
 
410
  /**
411
+ * Job Summary shortcode
412
  *
413
+ * @access public
414
+ * @param array $args
415
  * @return string
416
  */
417
  public function output_job_summary( $atts ) {
418
+ extract( shortcode_atts( array(
419
+ 'id' => '',
420
+ 'width' => '250px',
421
+ 'align' => 'left',
422
+ 'featured' => null, // True to show only featured, false to hide featured, leave null to show both (when leaving out id)
423
+ 'limit' => 1
424
+ ), $atts ) );
 
 
425
 
426
  ob_start();
427
 
428
  $args = array(
429
  'post_type' => 'job_listing',
430
+ 'post_status' => 'publish'
431
  );
432
 
433
+ if ( ! $id ) {
434
+ $args['posts_per_page'] = $limit;
435
  $args['orderby'] = 'rand';
436
+ if ( ! is_null( $featured ) ) {
437
+ $args['meta_query'] = array( array(
438
+ 'key' => '_featured',
439
+ 'value' => '1',
440
+ 'compare' => $featured ? '=' : '!='
441
+ ) );
 
 
442
  }
443
  } else {
444
+ $args['p'] = absint( $id );
445
  }
446
 
447
  $jobs = new WP_Query( $args );
448
 
449
+ if ( $jobs->have_posts() ) : ?>
450
+
451
+ <?php while ( $jobs->have_posts() ) : $jobs->the_post(); ?>
452
+
453
+ <div class="job_summary_shortcode align<?php echo $align ?>" style="width: <?php echo $width ? $width : auto; ?>">
454
+
455
+ <?php get_job_manager_template_part( 'content-summary', 'job_listing' ); ?>
456
+
457
+ </div>
458
+
459
+ <?php endwhile; ?>
460
+
461
+ <?php endif;
462
 
463
  wp_reset_postdata();
464
 
466
  }
467
 
468
  /**
469
+ * Show the application area
 
 
 
470
  */
471
  public function output_job_apply( $atts ) {
472
+ extract( shortcode_atts( array(
473
+ 'id' => ''
474
+ ), $atts ) );
 
 
 
475
 
476
  ob_start();
477
 
478
  $args = array(
479
  'post_type' => 'job_listing',
480
+ 'post_status' => 'publish'
481
  );
482
 
483
  if ( ! $id ) {
488
 
489
  $jobs = new WP_Query( $args );
490
 
491
+ if ( $jobs->have_posts() ) : ?>
492
+
493
+ <?php while ( $jobs->have_posts() ) :
494
  $jobs->the_post();
495
  $apply = get_the_job_application_method();
496
+ ?>
497
+
498
+ <?php do_action( 'job_manager_before_job_apply_' . absint( $id ) ); ?>
499
+
500
+ <?php if ( apply_filters( 'job_manager_show_job_apply_' . absint( $id ), true ) ) : ?>
501
+ <div class="job-manager-application-wrapper">
502
+ <?php do_action( 'job_manager_application_details_' . $apply->type, $apply ); ?>
503
+ </div>
504
+ <?php endif; ?>
505
+
506
+ <?php do_action( 'job_manager_after_job_apply_' . absint( $id ) ); ?>
507
+
508
+ <?php endwhile; ?>
509
+
510
+ <?php endif;
511
+
512
+ wp_reset_postdata();
513
 
514
  return ob_get_clean();
515
  }
516
  }
517
 
518
+ new WP_Job_Manager_Shortcodes();
includes/class-wp-job-manager-usage-tracking-data.php DELETED
@@ -1,315 +0,0 @@
1
- <?php
2
- /**
3
- * Usage tracking data
4
- *
5
- * @package Usage Tracking
6
- **/
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit;
10
- }
11
-
12
- /**
13
- * Supplies the usage tracking data for logging.
14
- *
15
- * @package Usage Tracking
16
- * @since 1.30.0
17
- */
18
- class WP_Job_Manager_Usage_Tracking_Data {
19
- /**
20
- * Get the usage tracking data to send.
21
- *
22
- * @since 1.30.0
23
- *
24
- * @return array Usage data.
25
- **/
26
- public static function get_usage_data() {
27
- $categories = 0;
28
- $count_posts = wp_count_posts( 'job_listing' );
29
-
30
- if ( taxonomy_exists( 'job_listing_category' ) ) {
31
- $categories = wp_count_terms( 'job_listing_category', array( 'hide_empty' => false ) );
32
- }
33
-
34
- return array(
35
- 'employers' => self::get_employer_count(),
36
- 'job_categories' => $categories,
37
- 'job_categories_desc' => self::get_job_category_has_description_count(),
38
- 'job_types' => wp_count_terms( 'job_listing_type', array( 'hide_empty' => false ) ),
39
- 'job_types_desc' => self::get_job_type_has_description_count(),
40
- 'job_types_emp_type' => self::get_job_type_has_employment_type_count(),
41
- 'jobs_type' => self::get_job_type_count(),
42
- 'jobs_logo' => self::get_company_logo_count(),
43
- 'jobs_status_expired' => isset( $count_posts->expired ) ? $count_posts->expired : 0,
44
- 'jobs_status_pending' => $count_posts->pending,
45
- 'jobs_status_pending_payment' => isset( $count_posts->pending_payment ) ? $count_posts->pending_payment : 0,
46
- 'jobs_status_preview' => isset( $count_posts->preview ) ? $count_posts->preview : 0,
47
- 'jobs_status_publish' => $count_posts->publish,
48
- 'jobs_location' => self::get_jobs_count_with_meta( '_job_location' ),
49
- 'jobs_app_contact' => self::get_jobs_count_with_meta( '_application' ),
50
- 'jobs_company_name' => self::get_jobs_count_with_meta( '_company_name' ),
51
- 'jobs_company_site' => self::get_jobs_count_with_meta( '_company_website' ),
52
- 'jobs_company_tagline' => self::get_jobs_count_with_meta( '_company_tagline' ),
53
- 'jobs_company_twitter' => self::get_jobs_count_with_meta( '_company_twitter' ),
54
- 'jobs_company_video' => self::get_jobs_count_with_meta( '_company_video' ),
55
- 'jobs_expiry' => self::get_jobs_count_with_meta( '_job_expires' ),
56
- 'jobs_featured' => self::get_jobs_count_with_checked_meta( '_featured' ),
57
- 'jobs_filled' => self::get_jobs_count_with_checked_meta( '_filled' ),
58
- 'jobs_freelance' => self::get_jobs_by_type_count( 'freelance' ),
59
- 'jobs_full_time' => self::get_jobs_by_type_count( 'full-time' ),
60
- 'jobs_intern' => self::get_jobs_by_type_count( 'internship' ),
61
- 'jobs_part_time' => self::get_jobs_by_type_count( 'part-time' ),
62
- 'jobs_temp' => self::get_jobs_by_type_count( 'temporary' ),
63
- 'jobs_by_guests' => self::get_jobs_by_guests(),
64
- );
65
- }
66
-
67
- /**
68
- * Get the total number of users with the "employer" role.
69
- *
70
- * @return int the number of "employers".
71
- */
72
- private static function get_employer_count() {
73
- $employer_query = new WP_User_Query(
74
- array(
75
- 'fields' => 'ID',
76
- 'role' => 'employer',
77
- )
78
- );
79
-
80
- return $employer_query->total_users;
81
- }
82
-
83
- /**
84
- * Get the number of job categories that have a description.
85
- *
86
- * @since 1.30.0
87
- *
88
- * @return int Number of job categories with a description.
89
- **/
90
- private static function get_job_category_has_description_count() {
91
- if ( ! taxonomy_exists( 'job_listing_category' ) ) {
92
- return 0;
93
- }
94
-
95
- $count = 0;
96
- $terms = get_terms(
97
- array(
98
- 'taxonomy' => 'job_listing_category',
99
- 'hide_empty' => false,
100
- )
101
- );
102
-
103
- foreach ( $terms as $term ) {
104
- $description = isset( $term->description ) ? trim( $term->description ) : '';
105
-
106
- if ( ! empty( $description ) ) {
107
- $count++;
108
- }
109
- }
110
-
111
- return $count;
112
- }
113
-
114
- /**
115
- * Get the number of job types that have a description.
116
- *
117
- * @since 1.30.0
118
- *
119
- * @return int Number of job types with a description.
120
- **/
121
- private static function get_job_type_has_description_count() {
122
- $count = 0;
123
- $terms = get_terms(
124
- array(
125
- 'taxonomy' => 'job_listing_type',
126
- 'hide_empty' => false,
127
- )
128
- );
129
-
130
- foreach ( $terms as $term ) {
131
- $description = isset( $term->description ) ? trim( $term->description ) : '';
132
-
133
- if ( ! empty( $description ) ) {
134
- $count++;
135
- }
136
- }
137
-
138
- return $count;
139
- }
140
-
141
- /**
142
- * Get the number of job types that have Employment Type set.
143
- *
144
- * @since 1.30.0
145
- *
146
- * @return int Number of job types with an employment type.
147
- **/
148
- private static function get_job_type_has_employment_type_count() {
149
- $count = 0;
150
- $terms = get_terms(
151
- array(
152
- 'taxonomy' => 'job_listing_type',
153
- 'hide_empty' => false,
154
- )
155
- );
156
-
157
- foreach ( $terms as $term ) {
158
- $employment_type = get_term_meta( $term->term_id, 'employment_type', true );
159
-
160
- if ( ! empty( $employment_type ) ) {
161
- $count++;
162
- }
163
- }
164
-
165
- return $count;
166
- }
167
-
168
- /**
169
- * Get the total number of published or expired jobs for a particular job type.
170
- *
171
- * @since 1.30.0
172
- *
173
- * @param string $job_type Job type to search for.
174
- *
175
- * @return int Number of published or expired jobs for a particular job type.
176
- **/
177
- private static function get_jobs_by_type_count( $job_type ) {
178
- $query = new WP_Query(
179
- array(
180
- 'post_type' => 'job_listing',
181
- 'post_status' => array( 'expired', 'publish' ),
182
- 'fields' => 'ids',
183
- 'tax_query' => array(
184
- array(
185
- 'field' => 'slug',
186
- 'taxonomy' => 'job_listing_type',
187
- 'terms' => $job_type,
188
- ),
189
- ),
190
- )
191
- );
192
-
193
- return $query->found_posts;
194
- }
195
-
196
- /**
197
- * Get the number of job listings that have a company logo.
198
- *
199
- * @since 1.30.0
200
- *
201
- * @return int Number of job listings with a company logo.
202
- */
203
- private static function get_company_logo_count() {
204
- $query = new WP_Query(
205
- array(
206
- 'post_type' => 'job_listing',
207
- 'post_status' => array( 'expired', 'publish' ),
208
- 'fields' => 'ids',
209
- 'meta_query' => array(
210
- array(
211
- 'key' => '_thumbnail_id',
212
- 'compare' => 'EXISTS',
213
- ),
214
- ),
215
- )
216
- );
217
-
218
- return $query->found_posts;
219
- }
220
-
221
- /**
222
- * Get the total number of job listings that have one or more job types selected.
223
- *
224
- * @since 1.30.0
225
- *
226
- * @return int Number of job listings associated with at least one job type.
227
- **/
228
- private static function get_job_type_count() {
229
- $query = new WP_Query(
230
- array(
231
- 'post_type' => 'job_listing',
232
- 'post_status' => array( 'expired', 'publish' ),
233
- 'fields' => 'ids',
234
- 'tax_query' => array(
235
- array(
236
- 'taxonomy' => 'job_listing_type',
237
- 'operator' => 'EXISTS',
238
- ),
239
- ),
240
- )
241
- );
242
-
243
- return $query->found_posts;
244
- }
245
-
246
- /**
247
- * Get the number of job listings where the given meta value is non-empty.
248
- *
249
- * @param string $meta_key the key for the meta value to check.
250
- *
251
- * @return int the number of job listings.
252
- */
253
- private static function get_jobs_count_with_meta( $meta_key ) {
254
- $query = new WP_Query(
255
- array(
256
- 'post_type' => 'job_listing',
257
- 'post_status' => array( 'publish', 'expired' ),
258
- 'fields' => 'ids',
259
- 'meta_query' => array(
260
- array(
261
- 'key' => $meta_key,
262
- 'value' => '[^[:space:]]',
263
- 'compare' => 'REGEXP',
264
- ),
265
- ),
266
- )
267
- );
268
-
269
- return $query->found_posts;
270
- }
271
-
272
- /**
273
- * Get the number of job listings where the given checkbox meta value is
274
- * checked.
275
- *
276
- * @param string $meta_key the key for the meta value to check.
277
- *
278
- * @return int the number of job listings.
279
- */
280
- private static function get_jobs_count_with_checked_meta( $meta_key ) {
281
- $query = new WP_Query(
282
- array(
283
- 'post_type' => 'job_listing',
284
- 'post_status' => array( 'publish', 'expired' ),
285
- 'fields' => 'ids',
286
- 'meta_query' => array(
287
- array(
288
- 'key' => $meta_key,
289
- 'value' => '1',
290
- ),
291
- ),
292
- )
293
- );
294
-
295
- return $query->found_posts;
296
- }
297
-
298
- /**
299
- * Get the number of job listings posted by guests.
300
- *
301
- * @return int the number of job listings.
302
- */
303
- private static function get_jobs_by_guests() {
304
- $query = new WP_Query(
305
- array(
306
- 'post_type' => 'job_listing',
307
- 'post_status' => array( 'publish', 'expired' ),
308
- 'fields' => 'ids',
309
- 'author__in' => array( 0 ),
310
- )
311
- );
312
-
313
- return $query->found_posts;
314
- }
315
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/class-wp-job-manager-usage-tracking.php DELETED
@@ -1,219 +0,0 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) {
4
- exit;
5
- }
6
-
7
- require dirname( __FILE__ ) . '/../lib/usage-tracking/class-usage-tracking-base.php';
8
-
9
- /**
10
- * WPJM Usage Tracking subclass.
11
- */
12
- class WP_Job_Manager_Usage_Tracking extends WP_Job_Manager_Usage_Tracking_Base {
13
-
14
- const WPJM_SETTING_NAME = 'job_manager_usage_tracking_enabled';
15
-
16
- const WPJM_TRACKING_INFO_URL = 'https://wpjobmanager.com/document/what-data-does-wpjm-track';
17
-
18
- /**
19
- * WP_Job_Manager_Usage_Tracking constructor.
20
- */
21
- protected function __construct() {
22
- parent::__construct();
23
-
24
- // Add filter for settings.
25
- add_filter( 'job_manager_settings', array( $this, 'add_setting_field' ) );
26
-
27
- // In the setup wizard, do not display the normal opt-in dialog.
28
- if ( isset( $_GET['page'] ) && 'job-manager-setup' === $_GET['page'] ) {
29
- remove_action( 'admin_notices', array( $this, 'maybe_display_tracking_opt_in' ) );
30
- }
31
- }
32
-
33
- /**
34
- * Implementation for abstract functions.
35
- */
36
-
37
- /**
38
- * Return the instance of this class.
39
- *
40
- * @return object
41
- */
42
- public static function get_instance() {
43
- return self::get_instance_for_subclass( get_class() );
44
- }
45
-
46
- /**
47
- * Get prefix for the usage data setting.
48
- *
49
- * @return string
50
- */
51
- protected function get_prefix() {
52
- return 'job_manager';
53
- }
54
-
55
- /**
56
- * Get prefix for the event sent for usage tracking.
57
- *
58
- * @return string
59
- */
60
- protected function get_event_prefix() {
61
- return 'wpjm';
62
- }
63
-
64
- /**
65
- * Get the text domain used in the plugin.
66
- *
67
- * @return string
68
- */
69
- protected function get_text_domain() {
70
- return 'wp-job-manager';
71
- }
72
-
73
- /**
74
- * Get the status of usage tracking.
75
- *
76
- * @return bool
77
- */
78
- public function get_tracking_enabled() {
79
- return get_option( self::WPJM_SETTING_NAME ) || false;
80
- }
81
-
82
- /**
83
- * Set whether or not usage tracking is enabled.
84
- *
85
- * @param bool $enable
86
- */
87
- public function set_tracking_enabled( $enable ) {
88
- update_option( self::WPJM_SETTING_NAME, $enable );
89
- }
90
-
91
- /**
92
- * Check if the current user can manage usage tracking settings.
93
- *
94
- * @return bool
95
- */
96
- protected function current_user_can_manage_tracking() {
97
- return current_user_can( 'manage_options' );
98
- }
99
-
100
- /**
101
- * Get the text to show in the opt-in dialog.
102
- *
103
- * @return string
104
- */
105
- protected function opt_in_dialog_text() {
106
- return sprintf(
107
- // translators: Placeholder %s is a URL to the document on wpjobmanager.com with info on usage tracking.
108
- __(
109
- 'We\'d love if you helped us make WP Job Manager better by allowing us to collect
110
- <a href="%s" target="_blank">usage tracking data</a>. No sensitive information is
111
- collected, and you can opt out at any time.',
112
- 'wp-job-manager'
113
- ), self::WPJM_TRACKING_INFO_URL
114
- );
115
- }
116
-
117
- /**
118
- * Check if we should track the status of a plugin.
119
- *
120
- * @param string $plugin_slug
121
- * @return bool
122
- */
123
- protected function do_track_plugin( $plugin_slug ) {
124
- if ( 1 === preg_match( '/^wp-job-manager/', $plugin_slug ) ) {
125
- return true;
126
- }
127
- $third_party_plugins = array(
128
- 'all-in-one-seo-pack',
129
- 'polylang',
130
- 'jetpack',
131
- 'wordpress-seo', // Yoast.
132
- 'sitepress-multilingual-cms', // WPML.
133
- 'bibblio-related-posts', // Related Posts for WordPress.
134
- );
135
- if ( in_array( $plugin_slug, $third_party_plugins, true ) ) {
136
- return true;
137
- }
138
- return false;
139
- }
140
-
141
-
142
- /*
143
- * Public functions.
144
- */
145
-
146
- /**
147
- * Hide the opt-in for enabling usage tracking.
148
- **/
149
- public function hide_tracking_opt_in() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod
150
- parent::hide_tracking_opt_in();
151
- }
152
-
153
- /**
154
- * Allowed html tags, used by wp_kses, for the translated opt-in dialog
155
- * text.
156
- *
157
- * @return array the html tags.
158
- **/
159
- public function opt_in_dialog_text_allowed_html() { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod
160
- return parent::opt_in_dialog_text_allowed_html();
161
- }
162
-
163
- /**
164
- * Get the opt-in text.
165
- *
166
- * @return string
167
- */
168
- public function opt_in_checkbox_text() {
169
- return sprintf(
170
-
171
- /*
172
- * translators: the href tag contains the URL for the page
173
- * telling users what data WPJM tracks.
174
- */
175
- __(
176
- 'Help us make WP Job Manager better by allowing us to collect
177
- <a href="%s" target="_blank">usage tracking data</a>.
178
- No sensitive information is collected.', 'wp-job-manager'
179
- ), self::WPJM_TRACKING_INFO_URL
180
- );
181
- }
182
-
183
-
184
- /**
185
- * Hooks.
186
- */
187
-
188
- /**
189
- * Add tracking setting field to general settings.
190
- *
191
- * @param array $fields
192
- * @return array
193
- */
194
- public function add_setting_field( $fields ) {
195
- $fields['general'][1][] = array(
196
- 'name' => self::WPJM_SETTING_NAME,
197
- 'std' => '0',
198
- 'type' => 'checkbox',
199
- 'desc' => '',
200
- 'label' => __( 'Enable Usage Tracking', 'wp-job-manager' ),
201
- 'cb_label' => $this->opt_in_checkbox_text(),
202
- );
203
-
204
- return $fields;
205
- }
206
-
207
-
208
- /**
209
- * Helpers.
210
- */
211
-
212
- /**
213
- * Clear options used for usage tracking.
214
- */
215
- public function clear_options() {
216
- delete_option( self::WPJM_SETTING_NAME );
217
- delete_option( $this->hide_tracking_opt_in_option_name );
218
- }
219
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/class-wp-job-manager-widget.php DELETED
@@ -1,220 +0,0 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) {
3
- exit;
4
- }
5
-
6
- /**
7
- * Job Manager Widget base.
8
- *
9
- * @package wp-job-manager
10
- * @since 1.0.0
11
- */
12
- class WP_Job_Manager_Widget extends WP_Widget {
13
-
14
- /**
15
- * Widget CSS class.
16
- *
17
- * @var string
18
- */
19
- public $widget_cssclass;
20
-
21
- /**
22
- * Widget description.
23
- *
24
- * @var string
25
- */
26
- public $widget_description;
27
-
28
- /**
29
- * Widget id.
30
- *
31
- * @var string
32
- */
33
- public $widget_id;
34
-
35
- /**
36
- * Widget name.
37
- *
38
- * @var string
39
- */
40
- public $widget_name;
41
-
42
- /**
43
- * Widget settings.
44
- *
45
- * @var array
46
- */
47
- public $settings;
48
-
49
- /**
50
- * Constructor.
51
- */
52
- public function __construct() {
53
- $this->register();
54
- }
55
-
56
- /**
57
- * Registers widget.
58
- */
59
- public function register() {
60
- $widget_ops = array(
61
- 'classname' => $this->widget_cssclass,
62
- 'description' => $this->widget_description,
63
- );
64
-
65
- parent::__construct( $this->widget_id, $this->widget_name, $widget_ops );
66
-
67
- add_action( 'save_post', array( $this, 'flush_widget_cache' ) );
68
- add_action( 'deleted_post', array( $this, 'flush_widget_cache' ) );
69
- add_action( 'switch_theme', array( $this, 'flush_widget_cache' ) );
70
- }
71
-
72
- /**
73
- * Checks for cached version of widget and outputs it if found.
74
- *
75
- * @param array $args
76
- * @return bool
77
- */
78
- public function get_cached_widget( $args ) {
79
- $cache = wp_cache_get( $this->widget_id, 'widget' );
80
-
81
- if ( ! is_array( $cache ) ) {
82
- $cache = array();
83
- }
84
-
85
- if ( isset( $cache[ $args['widget_id'] ] ) ) {
86
- echo $cache[ $args['widget_id'] ]; // WPCS: XSS ok.
87
- return true;
88
- }
89
-
90
- return false;
91
- }
92
-
93
- /**
94
- * Caches the widget.
95
- *
96
- * @param array $args
97
- * @param string $content
98
- */
99
- public function cache_widget( $args, $content ) {
100
- $cache[ $args['widget_id'] ] = $content;
101
-
102
- wp_cache_set( $this->widget_id, $cache, 'widget' );
103
- }
104
-
105
- /**
106
- * Flushes the cache for a widget.
107
- */
108
- public function flush_widget_cache() {
109
- wp_cache_delete( $this->widget_id, 'widget' );
110
- }
111
-
112
- /**
113
- * Updates a widget instance settings.
114
- *
115
- * @see WP_Widget->update
116
- * @param array $new_instance
117
- * @param array $old_instance
118
- * @return array
119
- */
120
- public function update( $new_instance, $old_instance ) {
121
- $instance = $old_instance;
122
-
123
- if ( ! $this->settings ) {
124
- return $instance;
125
- }
126
-
127
- foreach ( $this->settings as $key => $setting ) {
128
- $instance[ $key ] = sanitize_text_field( $new_instance[ $key ] );
129
- }
130
-
131
- $this->flush_widget_cache();
132
-
133
- return $instance;
134
- }
135
-
136
- /**
137
- * Displays widget setup form.
138
- *
139
- * @see WP_Widget->form
140
- * @param array $instance
141
- * @return void
142
- */
143
- public function form( $instance ) {
144
-
145
- if ( ! $this->settings ) {
146
- return;
147
- }
148
-
149
- foreach ( $this->settings as $key => $setting ) {
150
-
151
- $value = isset( $instance[ $key ] ) ? $instance[ $key ] : $setting['std'];
152
-
153
- switch ( $setting['type'] ) {
154
- case 'text':
155
- ?>
156
- <p>
157
- <label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo esc_html( $setting['label'] ); ?></label>
158
- <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="text" value="<?php echo esc_attr( $value ); ?>" />
159
- </p>
160
- <?php
161
- break;
162
- case 'number':
163
- ?>
164
- <p>
165
- <label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo esc_html( $setting['label'] ); ?></label>
166
- <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="number" step="<?php echo esc_attr( $setting['step'] ); ?>" min="<?php echo esc_attr( $setting['min'] ); ?>" max="<?php echo esc_attr( $setting['max'] ); ?>" value="<?php echo esc_attr( $value ); ?>" />
167
- </p>
168
- <?php
169
- break;
170
- case 'select':
171
- ?>
172
- <p>
173
- <label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo esc_html( $setting['label'] ); ?></label>
174
- <select class="widefat" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>">
175
- <?php foreach ( $setting['options'] as $option_key => $option_label ) : ?>
176
- <option value="<?php echo esc_attr( $option_key ); ?>" <?php selected( $value, $option_key ); ?>><?php echo esc_html( $option_label ); ?></option>
177
- <?php endforeach; ?></select>
178
- </p>
179
- <?php
180
- break;
181
- case 'checkbox':
182
- ?>
183
- <p>
184
- <label for="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>"><?php echo esc_html( $setting['label'] ); ?></label>
185
- <input class="checkbox" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( $key ) ); ?>" type="checkbox" value="1" <?php checked( $value, 1 ); ?> />
186
- </p>
187
- <?php
188
- break;
189
- }
190
- }
191
- }
192
-
193
- /**
194
- * Gets the instance with the default values for all settings.
195
- *
196
- * @return array
197
- */
198
- protected function get_default_instance() {
199
- $defaults = array();
200
- if ( ! empty( $this->settings ) ) {
201
- foreach ( $this->settings as $key => $setting ) {
202
- $defaults[ $key ] = null;
203
- if ( isset( $setting['std'] ) ) {
204
- $defaults[ $key ] = $setting['std'];
205
- }
206
- }
207
- }
208
- return $defaults;
209
- }
210
-
211
- /**
212
- * Echoes the widget content.
213
- *
214
- * @see WP_Widget
215
- *
216
- * @param array $args
217
- * @param array $instance
218
- */
219
- public function widget( $args, $instance ) {}
220
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/class-wp-job-manager-widgets.php ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( ! defined( 'ABSPATH' ) ) exit;
4
+
5
+ /**
6
+ * Job Manager Widget base
7
+ */
8
+ class WP_Job_Manager_Widget extends WP_Widget {
9
+
10
+ public $widget_cssclass;
11
+ public $widget_description;
12
+ public $widget_id;
13
+ public $widget_name;
14
+ public $settings;
15
+
16
+ /**
17
+ * Constructor
18
+ */
19
+ public function __construct() {
20
+ $this->register();
21
+ }
22
+
23
+ /**
24
+ * Register Widget
25
+ */
26
+ public function register() {
27
+ $widget_ops = array(
28
+ 'classname' => $this->widget_cssclass,
29
+ 'description' => $this->widget_description
30
+ );
31
+
32
+ parent::__construct( $this->widget_id, $this->widget_name, $widget_ops );
33
+
34
+ add_action( 'save_post', array( $this, 'flush_widget_cache' ) );
35
+ add_action( 'deleted_post', array( $this, 'flush_widget_cache' ) );
36
+ add_action( 'switch_theme', array( $this, 'flush_widget_cache' ) );
37
+ }
38
+
39
+ /**
40
+ * get_cached_widget function.
41
+ */
42
+ function get_cached_widget( $args ) {
43
+ $cache = wp_cache_get( $this->widget_id, 'widget' );
44
+
45
+ if ( ! is_array( $cache ) )
46
+ $cache = array();
47
+
48
+ if ( isset( $cache[ $args['widget_id'] ] ) ) {
49
+ echo $cache[ $args['widget_id'] ];
50
+ return true;
51
+ }
52
+
53
+ return false;
54
+ }
55
+
56
+ /**
57
+ * Cache the widget
58
+ */
59
+ public function cache_widget( $args, $content ) {
60
+ $cache[ $args['widget_id'] ] = $content;
61
+
62
+ wp_cache_set( $this->widget_id, $cache, 'widget' );
63
+ }
64
+
65
+ /**
66
+ * Flush the cache
67
+ * @return [type]
68
+ */
69
+ public function flush_widget_cache() {
70
+ wp_cache_delete( $this->widget_id, 'widget' );
71
+ }
72
+
73
+ /**
74
+ * update function.
75
+ *
76
+ * @see WP_Widget->update
77
+ * @access public
78
+ * @param array $new_instance
79
+ * @param array $old_instance
80
+ * @return array
81
+ */
82
+ function update( $new_instance, $old_instance ) {
83
+ $instance = $old_instance;
84
+
85
+ if ( ! $this->settings )
86
+ return $instance;
87
+
88
+ foreach ( $this->settings as $key => $setting ) {
89
+ $instance[ $key ] = sanitize_text_field( $new_instance[ $key ] );
90
+ }
91
+
92
+ $this->flush_widget_cache();
93
+
94
+ return $instance;
95
+ }
96
+
97
+ /**
98
+ * form function.
99
+ *
100
+ * @see WP_Widget->form
101
+ * @access public
102
+ * @param array $instance
103
+ * @return void
104
+ */
105
+ function form( $instance ) {
106
+
107
+ if ( ! $this->settings )
108
+ return;
109
+
110
+ foreach ( $this->settings as $key => $setting ) {
111
+
112
+ $value = isset( $instance[ $key ] ) ? $instance[ $key ] : $setting['std'];
113
+
114
+ switch ( $setting['type'] ) {
115
+ case 'text' :
116
+ ?>
117
+ <p>
118
+ <label for="<?php echo $this->get_field_id( $key ); ?>"><?php echo $setting['label']; ?></label>
119
+ <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo $this->get_field_name( $key ); ?>" type="text" value="<?php echo esc_attr( $value ); ?>" />
120
+ </p>
121
+ <?php
122
+ break;
123
+ case 'number' :
124
+ ?>
125
+ <p>
126
+ <label for="<?php echo $this->get_field_id( $key ); ?>"><?php echo $setting['label']; ?></label>
127
+ <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( $key ) ); ?>" name="<?php echo $this->get_field_name( $key ); ?>" type="number" step="<?php echo esc_attr( $setting['step'] ); ?>" min="<?php echo esc_attr( $setting['min'] ); ?>" max="<?php echo esc_attr( $setting['max'] ); ?>" value="<?php echo esc_attr( $value ); ?>" />
128
+ </p>
129
+ <?php
130
+ break;
131
+ }
132
+ }
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Recent Jobs Widget
138
+ */
139
+ class WP_Job_Manager_Widget_Recent_Jobs extends WP_Job_Manager_Widget {
140
+
141
+ /**
142
+ * Constructor
143
+ */
144
+ public function __construct() {
145
+ global $wp_post_types;
146
+
147
+ $this->widget_cssclass = 'job_manager widget_recent_jobs';
148
+ $this->widget_description = __( 'Display a list of recent listings on your site, optionally matching a keyword and location.', 'wp-job-manager' );
149
+ $this->widget_id = 'widget_recent_jobs';
150
+ $this->widget_name = sprintf( __( 'Recent %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name );
151
+ $this->settings = array(
152
+ 'title' => array(
153
+ 'type' => 'text',
154
+ 'std' => sprintf( __( 'Recent %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name ),
155
+ 'label' => __( 'Title', 'wp-job-manager' )
156
+ ),
157
+ 'keyword' => array(
158
+ 'type' => 'text',
159
+ 'std' => '',
160
+ 'label' => __( 'Keyword', 'wp-job-manager' )
161
+ ),
162
+ 'location' => array(
163
+ 'type' => 'text',
164
+ 'std' => '',
165
+ 'label' => __( 'Location', 'wp-job-manager' )
166
+ ),
167
+ 'number' => array(
168
+ 'type' => 'number',
169
+ 'step' => 1,
170
+ 'min' => 1,
171
+ 'max' => '',
172
+ 'std' => 10,
173
+ 'label' => __( 'Number of listings to show', 'wp-job-manager' )
174
+ )
175
+ );
176
+ $this->register();
177
+ }
178
+
179
+ /**
180
+ * widget function.
181
+ *
182
+ * @see WP_Widget
183
+ * @access public
184
+ * @param array $args
185
+ * @param array $instance
186
+ * @return void
187
+ */
188
+ public function widget( $args, $instance ) {
189
+ if ( $this->get_cached_widget( $args ) ) {
190
+ return;
191
+ }
192
+
193
+ ob_start();
194
+
195
+ extract( $args );
196
+
197
+ $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
198
+ $number = absint( $instance['number'] );
199
+ $jobs = get_job_listings( array(
200
+ 'search_location' => isset( $instance['location'] ) ? $instance['location'] : '',
201
+ 'search_keywords' => isset( $instance['keyword'] ) ? $instance['keyword'] : '',
202
+ 'posts_per_page' => $number,
203
+ 'orderby' => 'date',
204
+ 'order' => 'DESC',
205
+ ) );
206
+
207
+ if ( $jobs->have_posts() ) : ?>
208
+
209
+ <?php echo $before_widget; ?>
210
+
211
+ <?php if ( $title ) echo $before_title . $title . $after_title; ?>
212
+
213
+ <ul class="job_listings">
214
+
215
+ <?php while ( $jobs->have_posts() ) : $jobs->the_post(); ?>
216
+
217
+ <?php get_job_manager_template_part( 'content-widget', 'job_listing' ); ?>
218
+
219
+ <?php endwhile; ?>
220
+
221
+ </ul>
222
+
223
+ <?php echo $after_widget; ?>
224
+
225
+ <?php else : ?>
226
+
227
+ <?php get_job_manager_template_part( 'content-widget', 'no-jobs-found' ); ?>
228
+
229
+ <?php endif;
230
+
231
+ wp_reset_postdata();
232
+
233
+ $content = ob_get_clean();
234
+
235
+ echo $content;
236
+
237
+ $this->cache_widget( $args, $content );
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Featured Jobs Widget
243
+ */
244
+ class WP_Job_Manager_Widget_Featured_Jobs extends WP_Job_Manager_Widget {
245
+
246
+ /**
247
+ * Constructor
248
+ */
249
+ public function __construct() {
250
+ global $wp_post_types;
251
+
252
+ $this->widget_cssclass = 'job_manager widget_featured_jobs';
253
+ $this->widget_description = __( 'Display a list of featured listings on your site.', 'wp-job-manager' );
254
+ $this->widget_id = 'widget_featured_jobs';
255
+ $this->widget_name = sprintf( __( 'Featured %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name );
256
+ $this->settings = array(
257
+ 'title' => array(
258
+ 'type' => 'text',
259
+ 'std' => sprintf( __( 'Featured %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name ),
260
+ 'label' => __( 'Title', 'wp-job-manager' )
261
+ ),
262
+ 'number' => array(
263
+ 'type' => 'number',
264
+ 'step' => 1,
265
+ 'min' => 1,
266
+ 'max' => '',
267
+ 'std' => 10,
268
+ 'label' => __( 'Number of listings to show', 'wp-job-manager' )
269
+ )
270
+ );
271
+ $this->register();
272
+ }
273
+
274
+ /**
275
+ * widget function.
276
+ *
277
+ * @see WP_Widget
278
+ * @access public
279
+ * @param array $args
280
+ * @param array $instance
281
+ * @return void
282
+ */
283
+ public function widget( $args, $instance ) {
284
+ if ( $this->get_cached_widget( $args ) ) {
285
+ return;
286
+ }
287
+
288
+ ob_start();
289
+
290
+ extract( $args );
291
+
292
+ $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
293
+ $number = absint( $instance['number'] );
294
+ $jobs = get_job_listings( array(
295
+ 'posts_per_page' => $number,
296
+ 'orderby' => 'date',
297
+ 'order' => 'DESC',
298
+ 'featured' => true
299
+ ) );
300
+
301
+ if ( $jobs->have_posts() ) : ?>
302
+
303
+ <?php echo $before_widget; ?>
304
+
305
+ <?php if ( $title ) echo $before_title . $title . $after_title; ?>
306
+
307
+ <ul class="job_listings">
308
+
309
+ <?php while ( $jobs->have_posts() ) : $jobs->the_post(); ?>
310
+
311
+ <?php get_job_manager_template_part( 'content-widget', 'job_listing' ); ?>
312
+
313
+ <?php endwhile; ?>
314
+
315
+ </ul>
316
+
317
+ <?php echo $after_widget; ?>
318
+
319
+ <?php else : ?>
320
+
321
+ <?php get_job_manager_template_part( 'content-widget', 'no-jobs-found' ); ?>
322
+
323
+ <?php endif;
324
+
325
+ wp_reset_postdata();
326
+
327
+ $content = ob_get_clean();
328
+
329
+ echo $content;
330
+
331
+ $this->cache_widget( $args, $content );
332
+ }
333
+ }
334
+
335
+ register_widget( 'WP_Job_Manager_Widget_Recent_Jobs' );
336
+ register_widget( 'WP_Job_Manager_Widget_Featured_Jobs' );
includes/emails/class-wp-job-manager-email-admin-expiring-job.php DELETED
@@ -1,52 +0,0 @@
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 DELETED
@@ -1,91 +0,0 @@
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
- * Job listing post object.
53
- *
54
- * @var WP_Post $job
55
- */
56
- $job = $args['job'];
57
-
58
- // translators: Placeholder %s is the job listing post title.
59
- return sprintf( __( 'New Job Listing Submitted: %s', 'wp-job-manager' ), $job->post_title );
60
- }
61
-
62
- /**
63
- * Get `From:` address header value. Can be simple email or formatted `Firstname Lastname <email@example.com>`.
64
- *
65
- * @return string|bool Email from value or false to use WordPress' default.
66
- */
67
- public function get_from() {
68
- return false;
69
- }
70
-
71
- /**
72
- * Get array or comma-separated list of email addresses to send message.
73
- *
74
- * @return string|array
75
- */
76
- public function get_to() {
77
- return get_option( 'admin_email', false );
78
- }
79
-
80
- /**
81
- * Checks the arguments and returns whether the email notification is properly set up.
82
- *
83
- * @return bool
84
- */
85
- public function is_valid() {
86
- $args = $this->get_args();
87
- return isset( $args['job'] )
88
- && $args['job'] instanceof WP_Post
89
- && $this->get_to();
90
- }
91
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/emails/class-wp-job-manager-email-admin-updated-job.php DELETED
@@ -1,91 +0,0 @@
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
- * Job listing post object.
53
- *
54
- * @var WP_Post $job
55
- */
56
- $job = $args['job'];
57
-
58
- // translators: Placeholder %s is the job listing post title.
59
- return sprintf( __( 'Job Listing Updated: %s', 'wp-job-manager' ), $job->post_title );
60
- }
61
-
62
- /**
63
- * Get `From:` address header value. Can be simple email or formatted `Firstname Lastname <email@example.com>`.
64
- *
65
- * @return string|bool Email from value or false to use WordPress' default.
66
- */
67
- public function get_from() {
68
- return false;
69
- }
70
-
71
- /**
72
- * Get array or comma-separated list of email addresses to send message.
73
- *
74
- * @return string|array
75
- */
76
- public function get_to() {
77
- return get_option( 'admin_email', false );
78
- }
79
-
80
- /**
81
- * Checks the arguments and returns whether the email notification is properly set up.
82
- *
83
- * @return bool
84
- */
85
- public function is_valid() {
86
- $args = $this->get_args();
87
- return isset( $args['job'] )
88
- && $args['job'] instanceof WP_Post
89
- && $this->get_to();
90
- }
91
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/emails/class-wp-job-manager-email-employer-expiring-job.php DELETED
@@ -1,159 +0,0 @@
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
- * Job listing post object.
69
- *
70
- * @var WP_Post $job
71
- */
72
- $job = $args['job'];
73
-
74
- // translators: Placeholder %s is the job listing post title.
75
- return sprintf( __( 'Job Listing Expiring: %s', 'wp-job-manager' ), $job->post_title );
76
- }
77
-
78
- /**
79
- * Get `From:` address header value. Can be simple email or formatted `Firstname Lastname <email@example.com>`.
80
- *
81
- * @return string|bool Email from value or false to use WordPress' default.
82
- */
83
- public function get_from() {
84
- return false;
85
- }
86
-
87
- /**
88
- * Get array or comma-separated list of email addresses to send message.
89
- *
90
- * @return string|array
91
- */
92
- public function get_to() {
93
- $args = $this->get_args();
94
- return $args['author']->user_email;
95
- }
96
-
97
- /**
98
- * Expand arguments as necessary for the generation of the email.
99
- *
100
- * @param array $args
101
- * @return mixed
102
- */
103
- protected function prepare_args( $args ) {
104
- $args = parent::prepare_args( $args );
105
-
106
- if ( isset( $args['job'] ) ) {
107
- $args['expiring_today'] = false;
108
- $today = date( 'Y-m-d', current_time( 'timestamp' ) );
109
- $expiring_date = date( 'Y-m-d', strtotime( $args['job']->_job_expires ) );
110
- if ( ! empty( $args['job']->_job_expires ) && $today === $expiring_date ) {
111
- $args['expiring_today'] = true;
112
- }
113
- }
114
-
115
- return $args;
116
- }
117
-
118
- /**
119
- * Get the settings for this email notifications.
120
- *
121
- * @return array
122
- */
123
- public static function get_setting_fields() {
124
- $fields = parent::get_setting_fields();
125
- $fields[] = array(
126
- 'name' => self::SETTING_NOTICE_PERIOD_NAME,
127
- 'std' => self::SETTING_NOTICE_PERIOD_DEFAULT,
128
- 'label' => __( 'Notice Period', 'wp-job-manager' ),
129
- 'type' => 'number',
130
- 'after' => ' ' . __( 'days', 'wp-job-manager' ),
131
- 'attributes' => array( 'min' => 0 ),
132
- );
133
- return $fields;
134
- }
135
-
136
- /**
137
- * Is this email notification enabled by default?
138
- *
139
- * @return bool
140
- */
141
- public static function is_default_enabled() {
142
- return false;
143
- }
144
-
145
- /**
146
- * Checks the arguments and returns whether the email notification is properly set up.
147
- *
148
- * @return bool
149
- */
150
- public function is_valid() {
151
- $args = $this->get_args();
152
- return isset( $args['job'] )
153
- && $args['job'] instanceof WP_Post
154
- && isset( $args['author'] )
155
- && $args['author'] instanceof WP_User
156
- && ! empty( $args['author']->user_email );
157
- }
158
-
159
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/forms/class-wp-job-manager-form-edit-job.php CHANGED
@@ -1,43 +1,15 @@
1
  <?php
2
 
3
- require_once 'class-wp-job-manager-form-submit-job.php';
4
 
5
  /**
6
- * Handles the editing of Job Listings from the public facing frontend (from within `[job_dashboard]` shortcode).
7
- *
8
- * @package wp-job-manager
9
- * @since 1.0.0
10
- * @extends WP_Job_Manager_Form_Submit_Job
11
  */
12
  class WP_Job_Manager_Form_Edit_Job extends WP_Job_Manager_Form_Submit_Job {
13
 
14
- /**
15
- * Form name
16
- *
17
- * @var string
18
- */
19
- public $form_name = 'edit-job';
20
-
21
- /**
22
- * Messaged shown on save.
23
- *
24
- * @var bool|string
25
- */
26
- private $save_message = false;
27
-
28
- /**
29
- * Message shown on error.
30
- *
31
- * @var bool|string
32
- */
33
- private $save_error = false;
34
 
35
- /**
36
- * Instance
37
- *
38
- * @access protected
39
- * @var WP_Job_Manager_Form_Edit_Job The single instance of the class
40
- */
41
  protected static $_instance = null;
42
 
43
  /**
@@ -54,36 +26,18 @@ class WP_Job_Manager_Form_Edit_Job extends WP_Job_Manager_Form_Submit_Job {
54
  * Constructor
55
  */
56
  public function __construct() {
57
- add_action( 'wp', array( $this, 'submit_handler' ) );
58
- $this->job_id = ! empty( $_REQUEST['job_id'] ) ? absint( $_REQUEST['job_id'] ) : 0;
59
 
60
- if ( ! job_manager_user_can_edit_job( $this->job_id ) ) {
61
  $this->job_id = 0;
62
  }
63
-
64
- if ( ! empty( $this->job_id ) ) {
65
- $post_status = get_post_status( $this->job_id );
66
- if (
67
- ( 'publish' === $post_status && ! wpjm_user_can_edit_published_submissions() )
68
- || ( 'publish' !== $post_status && ! job_manager_user_can_edit_pending_submissions() )
69
- ) {
70
- $this->job_id = 0;
71
- }
72
- }
73
  }
74
 
75
  /**
76
- * Output function.
77
- *
78
- * @param array $atts
79
  */
80
  public function output( $atts = array() ) {
81
- if ( ! empty( $this->save_message ) ) {
82
- echo '<div class="job-manager-message">' . wp_kses_post( $this->save_message ) . '</div>';
83
- }
84
- if ( ! empty( $this->save_error ) ) {
85
- echo '<div class="job-manager-error">' . wp_kses_post( $this->save_error ) . '</div>';
86
- }
87
  $this->submit();
88
  }
89
 
@@ -93,8 +47,8 @@ class WP_Job_Manager_Form_Edit_Job extends WP_Job_Manager_Form_Submit_Job {
93
  public function submit() {
94
  $job = get_post( $this->job_id );
95
 
96
- if ( empty( $this->job_id ) ) {
97
- echo wp_kses_post( wpautop( __( 'Invalid listing', 'wp-job-manager' ) ) );
98
  return;
99
  }
100
 
@@ -124,34 +78,21 @@ class WP_Job_Manager_Form_Edit_Job extends WP_Job_Manager_Form_Submit_Job {
124
 
125
  $this->fields = apply_filters( 'submit_job_form_fields_get_job_data', $this->fields, $job );
126
 
127
- $this->enqueue_job_form_assets();
128
-
129
- $save_button_text = __( 'Save changes', 'wp-job-manager' );
130
- if ( 'publish' === get_post_status( $this->job_id )
131
- && wpjm_published_submission_edits_require_moderation() ) {
132
- $save_button_text = __( 'Submit changes for approval', 'wp-job-manager' );
133
- }
134
-
135
- $save_button_text = apply_filters( 'update_job_form_submit_button_text', $save_button_text );
136
-
137
- get_job_manager_template(
138
- 'job-submit.php',
139
- array(
140
- 'form' => $this->form_name,
141
- 'job_id' => $this->get_job_id(),
142
- 'action' => $this->get_action(),
143
- 'job_fields' => $this->get_fields( 'job' ),
144
- 'company_fields' => $this->get_fields( 'company' ),
145
- 'step' => $this->get_step(),
146
- 'submit_button_text' => $save_button_text,
147
- )
148
- );
149
  }
150
 
151
  /**
152
- * Submit Step is posted.
153
- *
154
- * @throws Exception When invalid fields are submitted.
155
  */
156
  public function submit_handler() {
157
  if ( empty( $_POST['submit_job'] ) ) {
@@ -160,73 +101,31 @@ class WP_Job_Manager_Form_Edit_Job extends WP_Job_Manager_Form_Submit_Job {
160
 
161
  try {
162
 
163
- // Get posted values.
164
  $values = $this->get_posted_fields();
165
 
166
- // Validate required.
167
- $validation_result = $this->validate_fields( $values );
168
- if ( is_wp_error( $validation_result ) ) {
169
- throw new Exception( $validation_result->get_error_message() );
170
  }
171
 
172
- $save_post_status = '';
173
- if ( wpjm_published_submission_edits_require_moderation() ) {
174
- $save_post_status = 'pending';
175
- }
176
- $original_post_status = get_post_status( $this->job_id );
177
-
178
- // Update the job.
179
- $this->save_job( $values['job']['job_title'], $values['job']['job_description'], $save_post_status, $values, false );
180
  $this->update_job_data( $values );
181
 
182
- // Successful.
183
- $save_message = __( 'Your changes have been saved.', 'wp-job-manager' );
184
- $post_status = get_post_status( $this->job_id );
185
-
186
- update_post_meta( $this->job_id, '_job_edited', time() );
187
-
188
- if ( 'publish' === $post_status ) {
189
- $save_message = $save_message . ' <a href="' . get_permalink( $this->job_id ) . '">' . __( 'View &rarr;', 'wp-job-manager' ) . '</a>';
190
- } elseif ( 'publish' === $original_post_status && 'pending' === $post_status ) {
191
- $save_message = __( 'Your changes have been submitted and your listing will be visible again once approved.', 'wp-job-manager' );
192
-
193
- /**
194
- * Resets the job expiration date when a user submits their job listing edit for approval.
195
- * Defaults to `false`.
196
- *
197
- * @since 1.29.0
198
- *
199
- * @param bool $reset_expiration If true, reset expiration date.
200
- */
201
- if ( apply_filters( 'job_manager_reset_listing_expiration_on_user_edit', false ) ) {
202
- delete_post_meta( $this->job_id, '_job_expires' );
203
- }
204
  }
205
 
206
- /**
207
- * Fire action after the user edits a job listing.
208
- *
209
- * @since 1.30.0
210
- *
211
- * @param int $job_id Job ID.
212
- * @param string $save_message Save message to filter.
213
- * @param array $values Submitted values for job listing.
214
- */
215
- do_action( 'job_manager_user_edit_job_listing', $this->job_id, $save_message, $values );
216
-
217
- /**
218
- * Change the message that appears when a user edits a job listing.
219
- *
220
- * @since 1.29.0
221
- *
222
- * @param string $save_message Save message to filter.
223
- * @param int $job_id Job ID.
224
- * @param array $values Submitted values for job listing.
225
- */
226
- $this->save_message = apply_filters( 'job_manager_update_job_listings_message', $save_message, $this->job_id, $values );
227
-
228
  } catch ( Exception $e ) {
229
- $this->save_error = $e->getMessage();
 
230
  }
231
  }
232
  }
1
  <?php
2
 
3
+ include_once( 'class-wp-job-manager-form-submit-job.php' );
4
 
5
  /**
6
+ * WP_Job_Manager_Form_Edit_Job class.
 
 
 
 
7
  */
8
  class WP_Job_Manager_Form_Edit_Job extends WP_Job_Manager_Form_Submit_Job {
9
 
10
+ public $form_name = 'edit-job';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ /** @var WP_Job_Manager_Form_Edit_Job The single instance of the class */
 
 
 
 
 
13
  protected static $_instance = null;
14
 
15
  /**
26
  * Constructor
27
  */
28
  public function __construct() {
29
+ $this->job_id = ! empty( $_REQUEST['job_id'] ) ? absint( $_REQUEST[ 'job_id' ] ) : 0;
 
30
 
31
+ if ( ! job_manager_user_can_edit_job( $this->job_id ) ) {
32
  $this->job_id = 0;
33
  }
 
 
 
 
 
 
 
 
 
 
34
  }
35
 
36
  /**
37
+ * output function.
 
 
38
  */
39
  public function output( $atts = array() ) {
40
+ $this->submit_handler();
 
 
 
 
 
41
  $this->submit();
42
  }
43
 
47
  public function submit() {
48
  $job = get_post( $this->job_id );
49
 
50
+ if ( empty( $this->job_id ) || ( $job->post_status !== 'publish' && ! job_manager_user_can_edit_pending_submissions() ) ) {
51
+ echo wpautop( __( 'Invalid listing', 'wp-job-manager' ) );
52
  return;
53
  }
54
 
78
 
79
  $this->fields = apply_filters( 'submit_job_form_fields_get_job_data', $this->fields, $job );
80
 
81
+ wp_enqueue_script( 'wp-job-manager-job-submission' );
82
+
83
+ get_job_manager_template( 'job-submit.php', array(
84
+ 'form' => $this->form_name,
85
+ 'job_id' => $this->get_job_id(),
86
+ 'action' => $this->get_action(),
87
+ 'job_fields' => $this->get_fields( 'job' ),
88
+ 'company_fields' => $this->get_fields( 'company' ),
89
+ 'step' => $this->get_step(),
90
+ 'submit_button_text' => __( 'Save changes', 'wp-job-manager' )
91
+ ) );
 
 
 
 
 
 
 
 
 
 
 
92
  }
93
 
94
  /**
95
+ * Submit Step is posted
 
 
96
  */
97
  public function submit_handler() {
98
  if ( empty( $_POST['submit_job'] ) ) {
101
 
102
  try {
103
 
104
+ // Get posted values
105
  $values = $this->get_posted_fields();
106
 
107
+ // Validate required
108
+ if ( is_wp_error( ( $return = $this->validate_fields( $values ) ) ) ) {
109
+ throw new Exception( $return->get_error_message() );
 
110
  }
111
 
112
+ // Update the job
113
+ $this->save_job( $values['job']['job_title'], $values['job']['job_description'], '', $values, false );
 
 
 
 
 
 
114
  $this->update_job_data( $values );
115
 
116
+ // Successful
117
+ switch ( get_post_status( $this->job_id ) ) {
118
+ case 'publish' :
119
+ echo '<div class="job-manager-message">' . __( 'Your changes have been saved.', 'wp-job-manager' ) . ' <a href="' . get_permalink( $this->job_id ) . '">' . __( 'View &rarr;', 'wp-job-manager' ) . '</a>' . '</div>';
120
+ break;
121
+ default :
122
+ echo '<div class="job-manager-message">' . __( 'Your changes have been saved.', 'wp-job-manager' ) . '</div>';
123
+ break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  }
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  } catch ( Exception $e ) {
127
+ echo '<div class="job-manager-error">' . $e->getMessage() . '</div>';
128
+ return;
129
  }
130
  }
131
  }
includes/forms/class-wp-job-manager-form-submit-job.php CHANGED
@@ -1,49 +1,19 @@
1
  <?php
2
 
3
  /**
4
- * Handles the editing of Job Listings from the public facing frontend (from within `[submit_job_form]` shortcode).
5
- *
6
- * @package wp-job-manager
7
- * @extends WP_Job_Manager_Form
8
- * @since 1.0.0
9
  */
10
  class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
11
 
12
- /**
13
- * Form name.
14
- *
15
- * @var string
16
- */
17
- public $form_name = 'submit-job';
18
-
19
- /**
20
- * Job listing ID.
21
- *
22
- * @access protected
23
- * @var int
24
- */
25
  protected $job_id;
26
-
27
- /**
28
- * Preview job (unused)
29
- *
30
- * @access protected
31
- * @var string
32
- */
33
  protected $preview_job;
34
 
35
- /**
36
- * Stores static instance of class.
37
- *
38
- * @access protected
39
- * @var WP_Job_Manager_Form_Submit_Job The single instance of the class
40
- */
41
  protected static $_instance = null;
42
 
43
  /**
44
- * Returns static instance of class.
45
- *
46
- * @return self
47
  */
48
  public static function instance() {
49
  if ( is_null( self::$_instance ) ) {
@@ -57,62 +27,49 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
57
  */
58
  public function __construct() {
59
  add_action( 'wp', array( $this, 'process' ) );
60
- if ( $this->use_recaptcha_field() ) {
61
- add_action( 'submit_job_form_end', array( $this, 'display_recaptcha_field' ) );
62
- add_action( 'submit_job_form_validate_fields', array( $this, 'validate_recaptcha_field' ) );
63
- }
64
 
65
- $this->steps = (array) apply_filters(
66
- 'submit_job_steps',
67
- array(
68
- 'submit' => array(
69
- 'name' => __( 'Submit Details', 'wp-job-manager' ),
70
- 'view' => array( $this, 'submit' ),
71
- 'handler' => array( $this, 'submit_handler' ),
72
- 'priority' => 10,
73
- ),
74
- 'preview' => array(
75
- 'name' => __( 'Preview', 'wp-job-manager' ),
76
- 'view' => array( $this, 'preview' ),
77
- 'handler' => array( $this, 'preview_handler' ),
78
- 'priority' => 20,
79
- ),
80
- 'done' => array(
81
- 'name' => __( 'Done', 'wp-job-manager' ),
82
- 'view' => array( $this, 'done' ),
83
- 'priority' => 30,
84
  ),
 
 
 
 
 
 
 
 
 
 
85
  )
86
- );
87
 
88
  uasort( $this->steps, array( $this, 'sort_by_priority' ) );
89
 
90
- // Get step/job.
91
  if ( isset( $_POST['step'] ) ) {
92
- $this->step = is_numeric( $_POST['step'] ) ? max( absint( $_POST['step'] ), 0 ) : array_search( intval( $_POST['step'] ), array_keys( $this->steps ), true );
93
  } elseif ( ! empty( $_GET['step'] ) ) {
94
- $this->step = is_numeric( $_GET['step'] ) ? max( absint( $_GET['step'] ), 0 ) : array_search( intval( $_GET['step'] ), array_keys( $this->steps ), true );
95
  }
96
 
97
- $this->job_id = ! empty( $_REQUEST['job_id'] ) ? absint( $_REQUEST['job_id'] ) : 0;
98
-
99
- if ( ! job_manager_user_can_edit_job( $this->job_id ) ) {
100
- $this->job_id = 0;
101
- }
102
 
103
  // Allow resuming from cookie.
104
- $this->resume_edit = false;
105
- if ( ! isset( $_GET['new'] ) && ( 'before' === get_option( 'job_manager_paid_listings_flow' ) || ! $this->job_id ) && ! empty( $_COOKIE['wp-job-manager-submitting-job-id'] ) && ! empty( $_COOKIE['wp-job-manager-submitting-job-key'] ) ) {
106
  $job_id = absint( $_COOKIE['wp-job-manager-submitting-job-id'] );
107
  $job_status = get_post_status( $job_id );
108
 
109
- if ( ( 'preview' === $job_status || 'pending_payment' === $job_status ) && get_post_meta( $job_id, '_submitting_key', true ) === $_COOKIE['wp-job-manager-submitting-job-key'] ) {
110
- $this->job_id = $job_id;
111
- $this->resume_edit = get_post_meta( $job_id, '_submitting_key', true );
112
  }
113
  }
114
 
115
- // Load job details.
116
  if ( $this->job_id ) {
117
  $job_status = get_post_status( $this->job_id );
118
  if ( 'expired' === $job_status ) {
@@ -120,7 +77,7 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
120
  $this->job_id = 0;
121
  $this->step = 0;
122
  }
123
- } elseif ( ! in_array( $job_status, apply_filters( 'job_manager_valid_submit_job_statuses', array( 'preview' ) ), true ) ) {
124
  $this->job_id = 0;
125
  $this->step = 0;
126
  }
@@ -128,8 +85,7 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
128
  }
129
 
130
  /**
131
- * Gets the submitted job ID.
132
- *
133
  * @return int
134
  */
135
  public function get_job_id() {
@@ -137,7 +93,7 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
137
  }
138
 
139
  /**
140
- * Initializes the fields used in the form.
141
  */
142
  public function init_fields() {
143
  if ( $this->fields ) {
@@ -146,173 +102,142 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
146
 
147
  $allowed_application_method = get_option( 'job_manager_allowed_application_method', '' );
148
  switch ( $allowed_application_method ) {
149
- case 'email':
150
  $application_method_label = __( 'Application email', 'wp-job-manager' );
151
  $application_method_placeholder = __( 'you@yourdomain.com', 'wp-job-manager' );
152
- $application_method_sanitizer = 'email';
153
- break;
154
- case 'url':
155
  $application_method_label = __( 'Application URL', 'wp-job-manager' );
156
  $application_method_placeholder = __( 'http://', 'wp-job-manager' );
157
- $application_method_sanitizer = 'url';
158
- break;
159
- default:
160
  $application_method_label = __( 'Application email/URL', 'wp-job-manager' );
161
  $application_method_placeholder = __( 'Enter an email address or website URL', 'wp-job-manager' );
162
- $application_method_sanitizer = 'url_or_email';
163
- break;
164
  }
165
 
166
- if ( job_manager_multi_job_type() ) {
167
- $job_type = 'term-multiselect';
168
- } else {
169
- $job_type = 'term-select';
170
- }
171
- $this->fields = apply_filters(
172
- 'submit_job_form_fields',
173
- array(
174
- 'job' => array(
175
- 'job_title' => array(
176
- 'label' => __( 'Job Title', 'wp-job-manager' ),
177
- 'type' => 'text',
178
- 'required' => true,
179
- 'placeholder' => '',
180
- 'priority' => 1,
181
- ),
182
- 'job_location' => array(
183
- 'label' => __( 'Location', 'wp-job-manager' ),
184
- 'description' => __( 'Leave this blank if the location is not important', 'wp-job-manager' ),
185
- 'type' => 'text',
186
- 'required' => false,
187
- 'placeholder' => __( 'e.g. "London"', 'wp-job-manager' ),
188
- 'priority' => 2,
189
- ),
190
- 'job_type' => array(
191
- 'label' => __( 'Job type', 'wp-job-manager' ),
192
- 'type' => $job_type,
193
- 'required' => true,
194
- 'placeholder' => __( 'Choose job type&hellip;', 'wp-job-manager' ),
195
- 'priority' => 3,
196
- 'default' => 'full-time',
197
- 'taxonomy' => 'job_listing_type',
198
- ),
199
- 'job_category' => array(
200
- 'label' => __( 'Job category', 'wp-job-manager' ),
201
- 'type' => 'term-multiselect',
202
- 'required' => true,
203
- 'placeholder' => '',
204
- 'priority' => 4,
205
- 'default' => '',
206
- 'taxonomy' => 'job_listing_category',
207
- ),
208
- 'job_description' => array(
209
- 'label' => __( 'Description', 'wp-job-manager' ),
210
- 'type' => 'wp-editor',
211
- 'required' => true,
212
- 'priority' => 5,
213
- ),
214
- 'application' => array(
215
- 'label' => $application_method_label,
216
- 'type' => 'text',
217
- 'sanitizer' => $application_method_sanitizer,
218
- 'required' => true,
219
- 'placeholder' => $application_method_placeholder,
220
- 'priority' => 6,
221
- ),
 
222
  ),
223
- 'company' => array(
224
- 'company_name' => array(
225
- 'label' => __( 'Company name', 'wp-job-manager' ),
226
- 'type' => 'text',
227
- 'required' => true,
228
- 'placeholder' => __( 'Enter the name of the company', 'wp-job-manager' ),
229
- 'priority' => 1,
230
- ),
231
- 'company_website' => array(
232
- 'label' => __( 'Website', 'wp-job-manager' ),
233
- 'type' => 'text',
234
- 'sanitizer' => 'url',
235
- 'required' => false,
236
- 'placeholder' => __( 'http://', 'wp-job-manager' ),
237
- 'priority' => 2,
238
- ),
239
- 'company_tagline' => array(
240
- 'label' => __( 'Tagline', 'wp-job-manager' ),
241
- 'type' => 'text',
242
- 'required' => false,
243
- 'placeholder' => __( 'Briefly describe your company', 'wp-job-manager' ),
244
- 'maxlength' => 64,
245
- 'priority' => 3,
246
- ),
247
- 'company_video' => array(
248
- 'label' => __( 'Video', 'wp-job-manager' ),
249
- 'type' => 'text',
250
- 'sanitizer' => 'url',
251
- 'required' => false,
252
- 'placeholder' => __( 'A link to a video about your company', 'wp-job-manager' ),
253
- 'priority' => 4,
254
- ),
255
- 'company_twitter' => array(
256
- 'label' => __( 'Twitter username', 'wp-job-manager' ),
257
- 'type' => 'text',
258
- 'required' => false,
259
- 'placeholder' => __( '@yourcompany', 'wp-job-manager' ),
260
- 'priority' => 5,
261
- ),
262
- 'company_logo' => array(
263
- 'label' => __( 'Logo', 'wp-job-manager' ),
264
- 'type' => 'file',
265
- 'required' => false,
266
- 'placeholder' => '',
267
- 'priority' => 6,
268
- 'ajax' => true,
269
- 'multiple' => false,
270
- 'allowed_mime_types' => array(
271
- 'jpg' => 'image/jpeg',
272
- 'jpeg' => 'image/jpeg',
273
- 'gif' => 'image/gif',
274
- 'png' => 'image/png',
275
- ),
276
- ),
277
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  )
279
- );
280
 
281
- if ( ! get_option( 'job_manager_enable_categories' ) || 0 === intval( wp_count_terms( 'job_listing_category' ) ) ) {
282
  unset( $this->fields['job']['job_category'] );
283
  }
284
- if ( ! get_option( 'job_manager_enable_types' ) || 0 === intval( wp_count_terms( 'job_listing_type' ) ) ) {
285
- unset( $this->fields['job']['job_type'] );
286
- }
287
  }
288
 
289
  /**
290
- * Use reCAPTCHA field on the form?
291
  *
292
- * @return bool
293
- */
294
- public function use_recaptcha_field() {
295
- if ( ! $this->is_recaptcha_available() ) {
296
- return false;
297
- }
298
- return 1 === absint( get_option( 'job_manager_enable_recaptcha_job_submission' ) );
299
- }
300
-
301
- /**
302
- * Validates the posted fields.
303
- *
304
- * @param array $values
305
- * @return bool|WP_Error True on success, WP_Error on failure.
306
- * @throws Exception Uploaded file is not a valid mime-type or other validation error.
307
  */
308
  protected function validate_fields( $values ) {
309
  foreach ( $this->fields as $group_key => $group_fields ) {
310
  foreach ( $group_fields as $key => $field ) {
311
  if ( $field['required'] && empty( $values[ $group_key ][ $key ] ) ) {
312
- // translators: Placeholder %s is the label for the required field.
313
  return new WP_Error( 'validation-error', sprintf( __( '%s is a required field', 'wp-job-manager' ), $field['label'] ) );
314
  }
315
- if ( ! empty( $field['taxonomy'] ) && in_array( $field['type'], array( 'term-checklist', 'term-select', 'term-multiselect' ), true ) ) {
316
  if ( is_array( $values[ $group_key ][ $key ] ) ) {
317
  $check_value = $values[ $group_key ][ $key ];
318
  } else {
@@ -320,11 +245,28 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
320
  }
321
  foreach ( $check_value as $term ) {
322
  if ( ! term_exists( $term, $field['taxonomy'] ) ) {
323
- // translators: Placeholder %s is the field label that is did not validate.
324
  return new WP_Error( 'validation-error', sprintf( __( '%s is invalid', 'wp-job-manager' ), $field['label'] ) );
325
  }
326
  }
327
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  if ( 'file' === $field['type'] && ! empty( $field['allowed_mime_types'] ) ) {
329
  if ( is_array( $values[ $group_key ][ $key ] ) ) {
330
  $check_value = array_filter( $values[ $group_key ][ $key ] );
@@ -336,9 +278,8 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
336
  $file_url = current( explode( '?', $file_url ) );
337
  $file_info = wp_check_filetype( $file_url );
338
 
339
- if ( ! is_numeric( $file_url ) && $file_info && ! in_array( $file_info['type'], $field['allowed_mime_types'], true ) ) {
340
- // translators: Placeholder %1$s is field label; %2$s is the file mime type; %3$s is the allowed mime-types.
341
- throw new Exception( sprintf( __( '"%1$s" (filetype %2$s) needs to be one of the following file types: %3$s', 'wp-job-manager' ), $field['label'], $file_info['ext'], implode( ', ', array_keys( $field['allowed_mime_types'] ) ) ) );
342
  }
343
  }
344
  }
@@ -346,28 +287,28 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
346
  }
347
  }
348
 
349
- // Application method.
350
  if ( isset( $values['job']['application'] ) && ! empty( $values['job']['application'] ) ) {
351
- $allowed_application_method = get_option( 'job_manager_allowed_application_method', '' );
352
  $values['job']['application'] = str_replace( ' ', '+', $values['job']['application'] );
353
  switch ( $allowed_application_method ) {
354
- case 'email':
355
  if ( ! is_email( $values['job']['application'] ) ) {
356
  throw new Exception( __( 'Please enter a valid application email address', 'wp-job-manager' ) );
357
  }
358
- break;
359
- case 'url':
360
- // Prefix http if needed.
361
  if ( ! strstr( $values['job']['application'], 'http:' ) && ! strstr( $values['job']['application'], 'https:' ) ) {
362
  $values['job']['application'] = 'http://' . $values['job']['application'];
363
  }
364
  if ( ! filter_var( $values['job']['application'], FILTER_VALIDATE_URL ) ) {
365
  throw new Exception( __( 'Please enter a valid application URL', 'wp-job-manager' ) );
366
  }
367
- break;
368
- default:
369
  if ( ! is_email( $values['job']['application'] ) ) {
370
- // Prefix http if needed.
371
  if ( ! strstr( $values['job']['application'], 'http:' ) && ! strstr( $values['job']['application'], 'https:' ) ) {
372
  $values['job']['application'] = 'http://' . $values['job']['application'];
373
  }
@@ -375,7 +316,7 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
375
  throw new Exception( __( 'Please enter a valid application email address or URL', 'wp-job-manager' ) );
376
  }
377
  }
378
- break;
379
  }
380
  }
381
 
@@ -383,43 +324,7 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
383
  }
384
 
385
  /**
386
- * Enqueues scripts and styles for editing and posting a job listing.
387
- */
388
- protected function enqueue_job_form_assets() {
389
- wp_enqueue_script( 'wp-job-manager-job-submission' );
390
- wp_enqueue_style( 'wp-job-manager-job-submission', JOB_MANAGER_PLUGIN_URL . '/assets/css/job-submission.css', array(), JOB_MANAGER_VERSION );
391
-
392
- // Register datepicker JS. It will be enqueued if needed when a date.
393
- // field is rendered.
394
- wp_register_script( 'wp-job-manager-datepicker', JOB_MANAGER_PLUGIN_URL . '/assets/js/datepicker.min.js', array( 'jquery', 'jquery-ui-datepicker' ), JOB_MANAGER_VERSION, true );
395
-
396
- // Localize scripts after the fields are rendered.
397
- add_action( 'submit_job_form_end', array( $this, 'localize_job_form_scripts' ) );
398
- }
399
-
400
- /**
401
- * Localize frontend scripts that have been enqueued. This should be called
402
- * after the fields are rendered, in case some of them enqueue new scripts.
403
- */
404
- public function localize_job_form_scripts() {
405
- if ( function_exists( 'wp_localize_jquery_ui_datepicker' ) ) {
406
- wp_localize_jquery_ui_datepicker();
407
- } else {
408
- wp_localize_script(
409
- 'wp-job-manager-datepicker',
410
- 'job_manager_datepicker',
411
- array(
412
- /* translators: jQuery date format, see http://api.jqueryui.com/datepicker/#utility-formatDate */
413
- 'date_format' => _x( 'yy-mm-dd', 'Date format for jQuery datepicker.', 'wp-job-manager' ),
414
- )
415
- );
416
- }
417
- }
418
-
419
- /**
420
- * Returns an array of the job types indexed by slug. (Unused)
421
- *
422
- * @return array
423
  */
424
  private function job_types() {
425
  $options = array();
@@ -431,45 +336,39 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
431
  }
432
 
433
  /**
434
- * Displays the form.
435
  */
436
  public function submit() {
437
  $this->init_fields();
438
 
439
- // Load data if neccessary.
440
  if ( $this->job_id ) {
441
  $job = get_post( $this->job_id );
442
  foreach ( $this->fields as $group_key => $group_fields ) {
443
  foreach ( $group_fields as $key => $field ) {
444
  switch ( $key ) {
445
- case 'job_title':
446
  $this->fields[ $group_key ][ $key ]['value'] = $job->post_title;
447
- break;
448
- case 'job_description':
449
  $this->fields[ $group_key ][ $key ]['value'] = $job->post_content;
450
- break;
451
- case 'job_type':
452
- $this->fields[ $group_key ][ $key ]['value'] = wp_get_object_terms( $job->ID, 'job_listing_type', array( 'fields' => 'ids' ) );
453
- if ( ! job_manager_multi_job_type() ) {
454
- $this->fields[ $group_key ][ $key ]['value'] = current( $this->fields[ $group_key ][ $key ]['value'] );
455
- }
456
- break;
457
- case 'job_category':
458
  $this->fields[ $group_key ][ $key ]['value'] = wp_get_object_terms( $job->ID, 'job_listing_category', array( 'fields' => 'ids' ) );
459
- break;
460
- case 'company_logo':
461
- $this->fields[ $group_key ][ $key ]['value'] = has_post_thumbnail( $job->ID ) ? get_post_thumbnail_id( $job->ID ) : get_post_meta( $job->ID, '_' . $key, true );
462
- break;
463
  default:
464
  $this->fields[ $group_key ][ $key ]['value'] = get_post_meta( $job->ID, '_' . $key, true );
465
- break;
466
  }
467
  }
468
  }
469
 
470
  $this->fields = apply_filters( 'submit_job_form_fields_get_job_data', $this->fields, $job );
471
 
472
- // Get user meta.
473
  } elseif ( is_user_logged_in() && empty( $_POST['submit_job'] ) ) {
474
  if ( ! empty( $this->fields['company'] ) ) {
475
  foreach ( $this->fields['company'] as $key => $field ) {
@@ -478,54 +377,48 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
478
  }
479
  if ( ! empty( $this->fields['job']['application'] ) ) {
480
  $allowed_application_method = get_option( 'job_manager_allowed_application_method', '' );
481
- if ( 'url' !== $allowed_application_method ) {
482
- $current_user = wp_get_current_user();
483
  $this->fields['job']['application']['value'] = $current_user->user_email;
484
  }
485
  }
486
  $this->fields = apply_filters( 'submit_job_form_fields_get_user_data', $this->fields, get_current_user_id() );
487
  }
488
 
489
- $this->enqueue_job_form_assets();
490
- get_job_manager_template(
491
- 'job-submit.php',
492
- array(
493
- 'form' => $this->form_name,
494
- 'job_id' => $this->get_job_id(),
495
- 'resume_edit' => $this->resume_edit,
496
- 'action' => $this->get_action(),
497
- 'job_fields' => $this->get_fields( 'job' ),
498
- 'company_fields' => $this->get_fields( 'company' ),
499
- 'step' => $this->get_step(),
500
- 'submit_button_text' => apply_filters( 'submit_job_form_submit_button_text', __( 'Preview', 'wp-job-manager' ) ),
501
- )
502
- );
503
  }
504
 
505
  /**
506
- * Handles the submission of form data.
507
- *
508
- * @throws Exception On validation error.
509
  */
510
  public function submit_handler() {
511
  try {
512
- // Init fields.
513
  $this->init_fields();
514
 
515
- // Get posted values.
516
  $values = $this->get_posted_fields();
517
 
518
  if ( empty( $_POST['submit_job'] ) ) {
519
  return;
520
  }
521
 
522
- // Validate required.
523
- $validation_status = $this->validate_fields( $values );
524
- if ( is_wp_error( $validation_status ) ) {
525
- throw new Exception( $validation_status->get_error_message() );
526
  }
527
 
528
- // Account creation.
529
  if ( ! is_user_logged_in() ) {
530
  $create_account = false;
531
 
@@ -534,40 +427,16 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
534
  if ( ! job_manager_generate_username_from_email() && empty( $_POST['create_account_username'] ) ) {
535
  throw new Exception( __( 'Please enter a username.', 'wp-job-manager' ) );
536
  }
537
- if ( ! wpjm_use_standard_password_setup_email() ) {
538
- if ( empty( $_POST['create_account_password'] ) ) {
539
- throw new Exception( __( 'Please enter a password.', 'wp-job-manager' ) );
540
- }
541
- }
542
  if ( empty( $_POST['create_account_email'] ) ) {
543
  throw new Exception( __( 'Please enter your email address.', 'wp-job-manager' ) );
544
  }
545
  }
546
-
547
- if ( ! wpjm_use_standard_password_setup_email() && ! empty( $_POST['create_account_password'] ) ) {
548
- if ( empty( $_POST['create_account_password_verify'] ) || $_POST['create_account_password_verify'] !== $_POST['create_account_password'] ) {
549
- throw new Exception( __( 'Passwords must match.', 'wp-job-manager' ) );
550
- }
551
- if ( ! wpjm_validate_new_password( $_POST['create_account_password'] ) ) {
552
- $password_hint = wpjm_get_password_rules_hint();
553
- if ( $password_hint ) {
554
- // translators: Placeholder %s is the password hint.
555
- throw new Exception( sprintf( __( 'Invalid Password: %s', 'wp-job-manager' ), $password_hint ) );
556
- } else {
557
- throw new Exception( __( 'Password is not valid.', 'wp-job-manager' ) );
558
- }
559
- }
560
- }
561
-
562
  if ( ! empty( $_POST['create_account_email'] ) ) {
563
- $create_account = wp_job_manager_create_account(
564
- array(
565
- 'username' => ( job_manager_generate_username_from_email() || empty( $_POST['create_account_username'] ) ) ? '' : $_POST['create_account_username'],
566
- 'password' => ( wpjm_use_standard_password_setup_email() || empty( $_POST['create_account_password'] ) ) ? '' : $_POST['create_account_password'],
567
- 'email' => $_POST['create_account_email'],
568
- 'role' => get_option( 'job_manager_registration_role' ),
569
- )
570
- );
571
  }
572
  }
573
 
@@ -577,14 +446,14 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
577
  }
578
 
579
  if ( job_manager_user_requires_account() && ! is_user_logged_in() ) {
580
- throw new Exception( __( 'You must be signed in to post a new listing.', 'wp-job-manager' ) );
581
  }
582
 
583
- // Update the job.
584
  $this->save_job( $values['job']['job_title'], $values['job']['job_description'], $this->job_id ? '' : 'preview', $values );
585
  $this->update_job_data( $values );
586
 
587
- // Successful, show next step.
588
  $this->step ++;
589
 
590
  } catch ( Exception $e ) {
@@ -594,50 +463,38 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
594
  }
595
 
596
  /**
597
- * Updates or creates a job listing from posted data.
598
  *
599
  * @param string $post_title
600
  * @param string $post_content
601
  * @param string $status
602
- * @param array $values
603
- * @param bool $update_slug
604
  */
605
  protected function save_job( $post_title, $post_content, $status = 'preview', $values = array(), $update_slug = true ) {
606
  $job_data = array(
607
  'post_title' => $post_title,
608
  'post_content' => $post_content,
609
  'post_type' => 'job_listing',
610
- 'comment_status' => 'closed',
611
  );
612
 
613
  if ( $update_slug ) {
614
- $job_slug = array();
615
 
616
- // Prepend with company name.
617
  if ( apply_filters( 'submit_job_form_prefix_post_name_with_company', true ) && ! empty( $values['company']['company_name'] ) ) {
618
  $job_slug[] = $values['company']['company_name'];
619
  }
620
 
621
- // Prepend location.
622
  if ( apply_filters( 'submit_job_form_prefix_post_name_with_location', true ) && ! empty( $values['job']['job_location'] ) ) {
623
  $job_slug[] = $values['job']['job_location'];
624
  }
625
 
626
- // Prepend with job type.
627
  if ( apply_filters( 'submit_job_form_prefix_post_name_with_job_type', true ) && ! empty( $values['job']['job_type'] ) ) {
628
- if ( ! job_manager_multi_job_type() ) {
629
- $job_slug[] = $values['job']['job_type'];
630
- } else {
631
- $terms = $values['job']['job_type'];
632
-
633
- foreach ( $terms as $term ) {
634
- $term = get_term_by( 'id', intval( $term ), 'job_listing_type' );
635
-
636
- if ( $term ) {
637
- $job_slug[] = $term->slug;
638
- }
639
- }
640
- }
641
  }
642
 
643
  $job_slug[] = $post_title;
@@ -668,32 +525,29 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
668
  }
669
 
670
  /**
671
- * Creates a file attachment.
672
- *
673
  * @param string $attachment_url
674
- * @return int attachment id.
675
  */
676
  protected function create_attachment( $attachment_url ) {
677
- include_once ABSPATH . 'wp-admin/includes/image.php';
678
- include_once ABSPATH . 'wp-admin/includes/media.php';
679
-
680
- $upload_dir = wp_upload_dir();
681
- $attachment_url = str_replace( array( $upload_dir['baseurl'], WP_CONTENT_URL, site_url( '/' ) ), array( $upload_dir['basedir'], WP_CONTENT_DIR, ABSPATH ), $attachment_url );
682
 
683
- if ( empty( $attachment_url ) || ! is_string( $attachment_url ) ) {
 
684
  return 0;
685
  }
686
 
687
- $attachment = array(
688
- 'post_title' => wpjm_get_the_job_title( $this->job_id ),
 
689
  'post_content' => '',
690
  'post_status' => 'inherit',
691
  'post_parent' => $this->job_id,
692
- 'guid' => $attachment_url,
693
  );
694
 
695
- $info = wp_check_filetype( $attachment_url );
696
- if ( $info ) {
697
  $attachment['post_mime_type'] = $info['type'];
698
  }
699
 
@@ -708,21 +562,21 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
708
  }
709
 
710
  /**
711
- * Sets job meta and terms based on posted values.
712
  *
713
  * @param array $values
714
  */
715
  protected function update_job_data( $values ) {
716
- // Set defaults.
717
  add_post_meta( $this->job_id, '_filled', 0, true );
718
  add_post_meta( $this->job_id, '_featured', 0, true );
719
 
720
  $maybe_attach = array();
721
 
722
- // Loop fields and save meta and term data.
723
  foreach ( $this->fields as $group_key => $group_fields ) {
724
  foreach ( $group_fields as $key => $field ) {
725
- // Save taxonomies.
726
  if ( ! empty( $field['taxonomy'] ) ) {
727
  if ( is_array( $values[ $group_key ][ $key ] ) ) {
728
  wp_set_object_terms( $this->job_id, $values[ $group_key ][ $key ], $field['taxonomy'], false );
@@ -730,21 +584,17 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
730
  wp_set_object_terms( $this->job_id, array( $values[ $group_key ][ $key ] ), $field['taxonomy'], false );
731
  }
732
 
733
- // Company logo is a featured image.
734
  } elseif ( 'company_logo' === $key ) {
735
  $attachment_id = is_numeric( $values[ $group_key ][ $key ] ) ? absint( $values[ $group_key ][ $key ] ) : $this->create_attachment( $values[ $group_key ][ $key ] );
736
- if ( empty( $attachment_id ) ) {
737
- delete_post_thumbnail( $this->job_id );
738
- } else {
739
- set_post_thumbnail( $this->job_id, $attachment_id );
740
- }
741
  update_user_meta( get_current_user_id(), '_company_logo', $attachment_id );
742
 
743
- // Save meta data.
744
  } else {
745
  update_post_meta( $this->job_id, '_' . $key, $values[ $group_key ][ $key ] );
746
 
747
- // Handle attachments.
748
  if ( 'file' === $field['type'] ) {
749
  if ( is_array( $values[ $group_key ][ $key ] ) ) {
750
  foreach ( $values[ $group_key ][ $key ] as $file_url ) {
@@ -760,25 +610,25 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
760
 
761
  $maybe_attach = array_filter( $maybe_attach );
762
 
763
- // Handle attachments.
764
- if ( count( $maybe_attach ) && apply_filters( 'job_manager_attach_uploaded_files', true ) ) {
765
- // Get attachments.
766
- $attachments = get_posts( 'post_parent=' . $this->job_id . '&post_type=attachment&fields=ids&numberposts=-1' );
767
  $attachment_urls = array();
768
 
769
- // Loop attachments already attached to the job.
770
  foreach ( $attachments as $attachment_id ) {
771
  $attachment_urls[] = wp_get_attachment_url( $attachment_id );
772
  }
773
 
774
  foreach ( $maybe_attach as $attachment_url ) {
775
- if ( ! in_array( $attachment_url, $attachment_urls, true ) ) {
776
  $this->create_attachment( $attachment_url );
777
  }
778
  }
779
  }
780
 
781
- // And user meta to save time in future.
782
  if ( is_user_logged_in() ) {
783
  update_user_meta( get_current_user_id(), '_company_name', isset( $values['company']['company_name'] ) ? $values['company']['company_name'] : '' );
784
  update_user_meta( get_current_user_id(), '_company_website', isset( $values['company']['company_website'] ) ? $values['company']['company_website'] : '' );
@@ -791,51 +641,48 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
791
  }
792
 
793
  /**
794
- * Displays preview of Job Listing.
795
  */
796
  public function preview() {
797
  global $post, $job_preview;
798
 
799
  if ( $this->job_id ) {
800
  $job_preview = true;
801
- $post = get_post( $this->job_id ); // WPCS: override ok.
802
  $post->post_status = 'preview';
803
 
804
  setup_postdata( $post );
805
 
806
- get_job_manager_template(
807
- 'job-preview.php',
808
- array(
809
- 'form' => $this,
810
- )
811
- );
812
 
813
  wp_reset_postdata();
814
  }
815
  }
816
 
817
  /**
818
- * Handles the preview step form response.
819
  */
820
  public function preview_handler() {
821
  if ( ! $_POST ) {
822
  return;
823
  }
824
 
825
- // Edit = show submit form again.
826
  if ( ! empty( $_POST['edit_job'] ) ) {
827
  $this->step --;
828
  }
829
 
830
- // Continue = change job status then show next screen.
831
  if ( ! empty( $_POST['continue'] ) ) {
832
  $job = get_post( $this->job_id );
833
 
834
- if ( in_array( $job->post_status, array( 'preview', 'expired' ), true ) ) {
835
- // Reset expiry.
836
  delete_post_meta( $job->ID, '_job_expires' );
837
 
838
- // Update job listing.
839
  $update_job = array();
840
  $update_job['ID'] = $job->ID;
841
  $update_job['post_status'] = apply_filters( 'submit_job_post_status', get_option( 'job_manager_submission_requires_approval' ) ? 'pending' : 'publish', $job );
@@ -851,7 +698,7 @@ class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
851
  }
852
 
853
  /**
854
- * Displays the final screen after a job listing has been submitted.
855
  */
856
  public function done() {
857
  do_action( 'job_manager_job_submitted', $this->job_id );
1
  <?php
2
 
3
  /**
4
+ * WP_Job_Manager_Form_Submit_Job class.
 
 
 
 
5
  */
6
  class WP_Job_Manager_Form_Submit_Job extends WP_Job_Manager_Form {
7
 
8
+ public $form_name = 'submit-job';
 
 
 
 
 
 
 
 
 
 
 
 
9
  protected $job_id;
 
 
 
 
 
 
 
10
  protected $preview_job;
11
 
12
+ /** @var WP_Job_Manager_Form_Submit_Job The single instance of the class */
 
 
 
 
 
13
  protected static $_instance = null;
14
 
15
  /**
16
+ * Main Instance
 
 
17
  */
18
  public static function instance() {
19
  if ( is_null( self::$_instance ) ) {
27
  */
28
  public function __construct() {
29
  add_action( 'wp', array( $this, 'process' ) );
 
 
 
 
30
 
31
+ $this->steps = (array) apply_filters( 'submit_job_steps', array(
32
+ 'submit' => array(
33
+ 'name' => __( 'Submit Details', 'wp-job-manager' ),
34
+ 'view' => array( $this, 'submit' ),
35
+ 'handler' => array( $this, 'submit_handler' ),
36
+ 'priority' => 10
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  ),
38
+ 'preview' => array(
39
+ 'name' => __( 'Preview', 'wp-job-manager' ),
40
+ 'view' => array( $this, 'preview' ),
41
+ 'handler' => array( $this, 'preview_handler' ),
42
+ 'priority' => 20
43
+ ),
44
+ 'done' => array(
45
+ 'name' => __( 'Done', 'wp-job-manager' ),
46
+ 'view' => array( $this, 'done' ),
47
+ 'priority' => 30
48
  )
49
+ ) );
50
 
51
  uasort( $this->steps, array( $this, 'sort_by_priority' ) );
52
 
53
+ // Get step/job
54
  if ( isset( $_POST['step'] ) ) {
55
+ $this->step = is_numeric( $_POST['step'] ) ? max( absint( $_POST['step'] ), 0 ) : array_search( $_POST['step'], array_keys( $this->steps ) );
56
  } elseif ( ! empty( $_GET['step'] ) ) {
57
+ $this->step = is_numeric( $_GET['step'] ) ? max( absint( $_GET['step'] ), 0 ) : array_search( $_GET['step'], array_keys( $this->steps ) );
58
  }
59
 
60
+ $this->job_id = ! empty( $_REQUEST['job_id'] ) ? absint( $_REQUEST[ 'job_id' ] ) : 0;
 
 
 
 
61
 
62
  // Allow resuming from cookie.
63
+ if ( ! $this->job_id && ! empty( $_COOKIE['wp-job-manager-submitting-job-id'] ) && ! empty( $_COOKIE['wp-job-manager-submitting-job-key'] ) ) {
 
64
  $job_id = absint( $_COOKIE['wp-job-manager-submitting-job-id'] );
65
  $job_status = get_post_status( $job_id );
66
 
67
+ if ( 'preview' === $job_status && get_post_meta( $job_id, '_submitting_key', true ) === $_COOKIE['wp-job-manager-submitting-job-key'] ) {
68
+ $this->job_id = $job_id;
 
69
  }
70
  }
71
 
72
+ // Load job details
73
  if ( $this->job_id ) {
74
  $job_status = get_post_status( $this->job_id );
75
  if ( 'expired' === $job_status ) {
77
  $this->job_id = 0;
78
  $this->step = 0;
79
  }
80
+ } elseif ( ! in_array( $job_status, apply_filters( 'job_manager_valid_submit_job_statuses', array( 'preview' ) ) ) ) {
81
  $this->job_id = 0;
82
  $this->step = 0;
83
  }
85
  }
86
 
87
  /**
88
+ * Get the submitted job ID
 
89
  * @return int
90
  */
91
  public function get_job_id() {
93
  }
94
 
95
  /**
96
+ * init_fields function.
97
  */
98
  public function init_fields() {
99
  if ( $this->fields ) {
102
 
103
  $allowed_application_method = get_option( 'job_manager_allowed_application_method', '' );
104
  switch ( $allowed_application_method ) {
105
+ case 'email' :
106
  $application_method_label = __( 'Application email', 'wp-job-manager' );
107
  $application_method_placeholder = __( 'you@yourdomain.com', 'wp-job-manager' );
108
+ break;
109
+ case 'url' :
 
110
  $application_method_label = __( 'Application URL', 'wp-job-manager' );
111
  $application_method_placeholder = __( 'http://', 'wp-job-manager' );
112
+ break;
113
+ default :
 
114
  $application_method_label = __( 'Application email/URL', 'wp-job-manager' );
115
  $application_method_placeholder = __( 'Enter an email address or website URL', 'wp-job-manager' );
116
+ break;
 
117
  }
118
 
119
+ $this->fields = apply_filters( 'submit_job_form_fields', array(
120
+ 'job' => array(
121
+ 'job_title' => array(
122
+ 'label' => __( 'Job Title', 'wp-job-manager' ),
123
+ 'type' => 'text',
124
+ 'required' => true,
125
+ 'placeholder' => '',
126
+ 'priority' => 1
127
+ ),
128
+ 'job_location' => array(
129
+ 'label' => __( 'Location', 'wp-job-manager' ),
130
+ 'description' => __( 'Leave this blank if the location is not important', 'wp-job-manager' ),
131
+ 'type' => 'text',
132
+ 'required' => false,
133
+ 'placeholder' => __( 'e.g. "London"', 'wp-job-manager' ),
134
+ 'priority' => 2
135
+ ),
136
+ 'job_type' => array(
137
+ 'label' => __( 'Job type', 'wp-job-manager' ),
138
+ 'type' => 'term-select',
139
+ 'required' => true,
140
+ 'placeholder' => '',
141
+ 'priority' => 3,
142
+ 'default' => 'full-time',
143
+ 'taxonomy' => 'job_listing_type'
144
+ ),
145
+ 'job_category' => array(
146
+ 'label' => __( 'Job category', 'wp-job-manager' ),
147
+ 'type' => 'term-multiselect',
148
+ 'required' => true,
149
+ 'placeholder' => '',
150
+ 'priority' => 4,
151
+ 'default' => '',
152
+ 'taxonomy' => 'job_listing_category'
153
+ ),
154
+ 'job_description' => array(
155
+ 'label' => __( 'Description', 'wp-job-manager' ),
156
+ 'type' => 'wp-editor',
157
+ 'required' => true,
158
+ 'placeholder' => '',
159
+ 'priority' => 5
160
+ ),
161
+ 'application' => array(
162
+ 'label' => $application_method_label,
163
+ 'type' => 'text',
164
+ 'required' => true,
165
+ 'placeholder' => $application_method_placeholder,
166
+ 'priority' => 6
167
+ )
168
+ ),
169
+ 'company' => array(
170
+ 'company_name' => array(
171
+ 'label' => __( 'Company name', 'wp-job-manager' ),
172
+ 'type' => 'text',
173
+ 'required' => true,
174
+ 'placeholder' => __( 'Enter the name of the company', 'wp-job-manager' ),
175
+ 'priority' => 1
176
  ),
177
+ 'company_website' => array(
178
+ 'label' => __( 'Website', 'wp-job-manager' ),
179
+ 'type' => 'text',
180
+ 'required' => false,
181
+ 'placeholder' => __( 'http://', 'wp-job-manager' ),
182
+ 'priority' => 2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  ),
184
+ 'company_tagline' => array(
185
+ 'label' => __( 'Tagline', 'wp-job-manager' ),
186
+ 'type' => 'text',
187
+ 'required' => false,
188
+ 'placeholder' => __( 'Briefly describe your company', 'wp-job-manager' ),
189
+ 'maxlength' => 64,
190
+ 'priority' => 3
191
+ ),
192
+ 'company_video' => array(
193
+ 'label' => __( 'Video', 'wp-job-manager' ),
194
+ 'type' => 'text',
195
+ 'required' => false,
196
+ 'placeholder' => __( 'A link to a video about your company', 'wp-job-manager' ),
197
+ 'priority' => 4
198
+ ),
199
+ 'company_twitter' => array(
200
+ 'label' => __( 'Twitter username', 'wp-job-manager' ),
201
+ 'type' => 'text',
202
+ 'required' => false,
203
+ 'placeholder' => __( '@yourcompany', 'wp-job-manager' ),
204
+ 'priority' => 5
205
+ ),
206
+ 'company_logo' => array(
207
+ 'label' => __( 'Logo', 'wp-job-manager' ),
208
+ 'type' => 'file',
209
+ 'required' => false,
210
+ 'placeholder' => '',
211
+ 'priority' => 6,
212
+ 'ajax' => true,
213
+ 'multiple' => false,
214
+ 'allowed_mime_types' => array(
215
+ 'jpg' => 'image/jpeg',
216
+ 'jpeg' => 'image/jpeg',
217
+ 'gif' => 'image/gif',
218
+ 'png' => 'image/png'
219
+ )
220
+ )
221
  )
222
+ ) );
223
 
224
+ if ( ! get_option( 'job_manager_enable_categories' ) || wp_count_terms( 'job_listing_category' ) == 0 ) {
225
  unset( $this->fields['job']['job_category'] );
226
  }
 
 
 
227
  }
228
 
229
  /**
230
+ * Validate the posted fields
231
  *
232
+ * @return bool on success, WP_ERROR on failure
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  */
234
  protected function validate_fields( $values ) {
235
  foreach ( $this->fields as $group_key => $group_fields ) {
236
  foreach ( $group_fields as $key => $field ) {
237
  if ( $field['required'] && empty( $values[ $group_key ][ $key ] ) ) {
 
238
  return new WP_Error( 'validation-error', sprintf( __( '%s is a required field', 'wp-job-manager' ), $field['label'] ) );
239
  }
240
+ if ( ! empty( $field['taxonomy'] ) && in_array( $field['type'], array( 'term-checklist', 'term-select', 'term-multiselect' ) ) ) {
241
  if ( is_array( $values[ $group_key ][ $key ] ) ) {
242
  $check_value = $values[ $group_key ][ $key ];
243
  } else {
245
  }
246
  foreach ( $check_value as $term ) {
247
  if ( ! term_exists( $term, $field['taxonomy'] ) ) {
 
248
  return new WP_Error( 'validation-error', sprintf( __( '%s is invalid', 'wp-job-manager' ), $field['label'] ) );
249
  }
250
  }
251
  }
252
+ if ( 'file' === $field['type'] ) {
253
+ if ( is_array( $values[ $group_key ][ $key ] ) ) {
254
+ $check_value = array_filter( $values[ $group_key ][ $key ] );
255
+ } else {
256
+ $check_value = array_filter( array( $values[ $group_key ][ $key ] ) );
257
+ }
258
+ if ( ! empty( $check_value ) ) {
259
+ foreach ( $check_value as $file_url ) {
260
+ if ( is_numeric( $file_url ) ) {
261
+ continue;
262
+ }
263
+ $file_url = esc_url( $file_url, array( 'http', 'https' ) );
264
+ if ( empty( $file_url ) ) {
265
+ throw new Exception( __( 'Invalid attachment provided.', 'wp-job-manager' ) );
266
+ }
267
+ }
268
+ }
269
+ }
270
  if ( 'file' === $field['type'] && ! empty( $field['allowed_mime_types'] ) ) {
271
  if ( is_array( $values[ $group_key ][ $key ] ) ) {
272
  $check_value = array_filter( $values[ $group_key ][ $key ] );
278
  $file_url = current( explode( '?', $file_url ) );
279
  $file_info = wp_check_filetype( $file_url );
280
 
281
+ if ( ! is_numeric( $file_url ) && $file_info && ! in_array( $file_info['type'], $field['allowed_mime_types'] ) ) {
282
+ throw new Exception( sprintf( __( '"%s" (filetype %s) needs to be one of the following file types: %s', 'wp-job-manager' ), $field['label'], $file_info['ext'], implode( ', ', array_keys( $field['allowed_mime_types'] ) ) ) );
 
283
  }
284
  }
285
  }
287
  }
288
  }
289
 
290
+ // Application method
291
  if ( isset( $values['job']['application'] ) && ! empty( $values['job']['application'] ) ) {
292
+ $allowed_application_method = get_option( 'job_manager_allowed_application_method', '' );
293
  $values['job']['application'] = str_replace( ' ', '+', $values['job']['application'] );
294
  switch ( $allowed_application_method ) {
295
+ case 'email' :
296
  if ( ! is_email( $values['job']['application'] ) ) {
297
  throw new Exception( __( 'Please enter a valid application email address', 'wp-job-manager' ) );
298
  }
299
+ break;
300
+ case 'url' :
301
+ // Prefix http if needed
302
  if ( ! strstr( $values['job']['application'], 'http:' ) && ! strstr( $values['job']['application'], 'https:' ) ) {
303
  $values['job']['application'] = 'http://' . $values['job']['application'];
304
  }
305
  if ( ! filter_var( $values['job']['application'], FILTER_VALIDATE_URL ) ) {
306
  throw new Exception( __( 'Please enter a valid application URL', 'wp-job-manager' ) );
307
  }
308
+ break;
309
+ default :
310
  if ( ! is_email( $values['job']['application'] ) ) {
311
+ // Prefix http if needed
312
  if ( ! strstr( $values['job']['application'], 'http:' ) && ! strstr( $values['job']['application'], 'https:' ) ) {
313
  $values['job']['application'] = 'http://' . $values['job']['application'];
314
  }
316
  throw new Exception( __( 'Please enter a valid application email address or URL', 'wp-job-manager' ) );
317
  }
318
  }
319
+ break;
320
  }
321
  }
322
 
324
  }
325
 
326
  /**
327
+ * job_types function.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  */
329
  private function job_types() {
330
  $options = array();
336
  }
337
 
338
  /**
339
+ * Submit Step
340
  */
341
  public function submit() {
342
  $this->init_fields();
343
 
344
+ // Load data if neccessary
345
  if ( $this->job_id ) {
346
  $job = get_post( $this->job_id );
347
  foreach ( $this->fields as $group_key => $group_fields ) {
348
  foreach ( $group_fields as $key => $field ) {
349
  switch ( $key ) {
350
+ case 'job_title' :
351
  $this->fields[ $group_key ][ $key ]['value'] = $job->post_title;
352
+ break;
353
+ case 'job_description' :
354
  $this->fields[ $group_key ][ $key ]['value'] = $job->post_content;
355
+ break;
356
+ case 'job_type' :
357
+ $this->fields[ $group_key ][ $key ]['value'] = current( wp_get_object_terms( $job->ID, 'job_listing_type', array( 'fields' => 'ids' ) ) );
358
+ break;
359
+ case 'job_category' :
 
 
 
360
  $this->fields[ $group_key ][ $key ]['value'] = wp_get_object_terms( $job->ID, 'job_listing_category', array( 'fields' => 'ids' ) );
361
+ break;
 
 
 
362
  default:
363
  $this->fields[ $group_key ][ $key ]['value'] = get_post_meta( $job->ID, '_' . $key, true );
364
+ break;
365
  }
366
  }
367
  }
368
 
369
  $this->fields = apply_filters( 'submit_job_form_fields_get_job_data', $this->fields, $job );
370
 
371
+ // Get user meta
372
  } elseif ( is_user_logged_in() && empty( $_POST['submit_job'] ) ) {
373
  if ( ! empty( $this->fields['company'] ) ) {
374
  foreach ( $this->fields['company'] as $key => $field ) {
377
  }
378
  if ( ! empty( $this->fields['job']['application'] ) ) {
379
  $allowed_application_method = get_option( 'job_manager_allowed_application_method', '' );
380
+ if ( $allowed_application_method !== 'url' ) {
381
+ $current_user = wp_get_current_user();
382
  $this->fields['job']['application']['value'] = $current_user->user_email;
383
  }
384
  }
385
  $this->fields = apply_filters( 'submit_job_form_fields_get_user_data', $this->fields, get_current_user_id() );
386
  }
387
 
388
+ wp_enqueue_script( 'wp-job-manager-job-submission' );
389
+
390
+ get_job_manager_template( 'job-submit.php', array(
391
+ 'form' => $this->form_name,
392
+ 'job_id' => $this->get_job_id(),
393
+ 'action' => $this->get_action(),
394
+ 'job_fields' => $this->get_fields( 'job' ),
395
+ 'company_fields' => $this->get_fields( 'company' ),
396
+ 'step' => $this->get_step(),
397
+ 'submit_button_text' => apply_filters( 'submit_job_form_submit_button_text', __( 'Preview', 'wp-job-manager' ) )
398
+ ) );
 
 
 
399
  }
400
 
401
  /**
402
+ * Submit Step is posted
 
 
403
  */
404
  public function submit_handler() {
405
  try {
406
+ // Init fields
407
  $this->init_fields();
408
 
409
+ // Get posted values
410
  $values = $this->get_posted_fields();
411
 
412
  if ( empty( $_POST['submit_job'] ) ) {
413
  return;
414
  }
415
 
416
+ // Validate required
417
+ if ( is_wp_error( ( $return = $this->validate_fields( $values ) ) ) ) {
418
+ throw new Exception( $return->get_error_message() );
 
419
  }
420
 
421
+ // Account creation
422
  if ( ! is_user_logged_in() ) {
423
  $create_account = false;
424
 
427
  if ( ! job_manager_generate_username_from_email() && empty( $_POST['create_account_username'] ) ) {
428
  throw new Exception( __( 'Please enter a username.', 'wp-job-manager' ) );
429
  }
 
 
 
 
 
430
  if ( empty( $_POST['create_account_email'] ) ) {
431
  throw new Exception( __( 'Please enter your email address.', 'wp-job-manager' ) );
432
  }
433
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  if ( ! empty( $_POST['create_account_email'] ) ) {
435
+ $create_account = wp_job_manager_create_account( array(
436
+ 'username' => empty( $_POST['create_account_username'] ) ? '' : $_POST['create_account_username'],
437
+ 'email' => $_POST['create_account_email'],
438
+ 'role' => get_option( 'job_manager_registration_role' )
439
+ ) );
 
 
 
440
  }
441
  }
442
 
446
  }
447
 
448
  if ( job_manager_user_requires_account() && ! is_user_logged_in() ) {
449
+ throw new Exception( __( 'You must be signed in to post a new listing.' ) );
450
  }
451
 
452
+ // Update the job
453
  $this->save_job( $values['job']['job_title'], $values['job']['job_description'], $this->job_id ? '' : 'preview', $values );
454
  $this->update_job_data( $values );
455
 
456
+ // Successful, show next step
457
  $this->step ++;
458
 
459
  } catch ( Exception $e ) {
463
  }
464
 
465
  /**
466
+ * Update or create a job listing from posted data
467
  *
468
  * @param string $post_title
469
  * @param string $post_content
470
  * @param string $status
471
+ * @param array $values
472
+ * @param bool $update_slug
473
  */
474
  protected function save_job( $post_title, $post_content, $status = 'preview', $values = array(), $update_slug = true ) {
475
  $job_data = array(
476
  'post_title' => $post_title,
477
  'post_content' => $post_content,
478
  'post_type' => 'job_listing',
479
+ 'comment_status' => 'closed'
480
  );
481
 
482
  if ( $update_slug ) {
483
+ $job_slug = array();
484
 
485
+ // Prepend with company name
486
  if ( apply_filters( 'submit_job_form_prefix_post_name_with_company', true ) && ! empty( $values['company']['company_name'] ) ) {
487
  $job_slug[] = $values['company']['company_name'];
488
  }
489
 
490
+ // Prepend location
491
  if ( apply_filters( 'submit_job_form_prefix_post_name_with_location', true ) && ! empty( $values['job']['job_location'] ) ) {
492
  $job_slug[] = $values['job']['job_location'];
493
  }
494
 
495
+ // Prepend with job type
496
  if ( apply_filters( 'submit_job_form_prefix_post_name_with_job_type', true ) && ! empty( $values['job']['job_type'] ) ) {
497
+ $job_slug[] = $values['job']['job_type'];
 
 
 
 
 
 
 
 
 
 
 
 
498
  }
499
 
500
  $job_slug[] = $post_title;
525
  }
526
 
527
  /**
528
+ * Create an attachment
 
529
  * @param string $attachment_url
530
+ * @return int attachment id
531
  */
532
  protected function create_attachment( $attachment_url ) {
533
+ include_once( ABSPATH . 'wp-admin/includes/image.php' );
534
+ include_once( ABSPATH . 'wp-admin/includes/media.php' );
 
 
 
535
 
536
+ $attachment_url = esc_url( $attachment_url, array( 'http', 'https' ) );
537
+ if ( empty( $attachment_url ) ) {
538
  return 0;
539
  }
540
 
541
+ $attachment_url = str_replace( array( WP_CONTENT_URL, site_url() ), array( WP_CONTENT_DIR, ABSPATH ), $attachment_url );
542
+ $attachment = array(
543
+ 'post_title' => get_the_title( $this->job_id ),
544
  'post_content' => '',
545
  'post_status' => 'inherit',
546
  'post_parent' => $this->job_id,
547
+ 'guid' => $attachment_url
548
  );
549
 
550
+ if ( $info = wp_check_filetype( $attachment_url ) ) {
 
551
  $attachment['post_mime_type'] = $info['type'];
552
  }
553
 
562
  }
563
 
564
  /**
565
+ * Set job meta + terms based on posted values
566
  *
567
  * @param array $values
568
  */
569
  protected function update_job_data( $values ) {
570
+ // Set defaults
571
  add_post_meta( $this->job_id, '_filled', 0, true );
572
  add_post_meta( $this->job_id, '_featured', 0, true );
573
 
574
  $maybe_attach = array();
575
 
576
+ // Loop fields and save meta and term data
577
  foreach ( $this->fields as $group_key => $group_fields ) {
578
  foreach ( $group_fields as $key => $field ) {
579
+ // Save taxonomies
580
  if ( ! empty( $field['taxonomy'] ) ) {
581
  if ( is_array( $values[ $group_key ][ $key ] ) ) {
582
  wp_set_object_terms( $this->job_id, $values[ $group_key ][ $key ], $field['taxonomy'], false );
584
  wp_set_object_terms( $this->job_id, array( $values[ $group_key ][ $key ] ), $field['taxonomy'], false );
585
  }
586
 
587
+ // Company logo is a featured image
588
  } elseif ( 'company_logo' === $key ) {
589
  $attachment_id = is_numeric( $values[ $group_key ][ $key ] ) ? absint( $values[ $group_key ][ $key ] ) : $this->create_attachment( $values[ $group_key ][ $key ] );
590
+ set_post_thumbnail( $this->job_id, $attachment_id );
 
 
 
 
591
  update_user_meta( get_current_user_id(), '_company_logo', $attachment_id );
592
 
593
+ // Save meta data
594
  } else {
595
  update_post_meta( $this->job_id, '_' . $key, $values[ $group_key ][ $key ] );
596
 
597
+ // Handle attachments
598
  if ( 'file' === $field['type'] ) {
599
  if ( is_array( $values[ $group_key ][ $key ] ) ) {
600
  foreach ( $values[ $group_key ][ $key ] as $file_url ) {
610
 
611
  $maybe_attach = array_filter( $maybe_attach );
612
 
613
+ // Handle attachments
614
+ if ( sizeof( $maybe_attach ) && apply_filters( 'job_manager_attach_uploaded_files', true ) ) {
615
+ // Get attachments
616
+ $attachments = get_posts( 'post_parent=' . $this->job_id . '&post_type=attachment&fields=ids&post_mime_type=image&numberposts=-1' );
617
  $attachment_urls = array();
618
 
619
+ // Loop attachments already attached to the job
620
  foreach ( $attachments as $attachment_id ) {
621
  $attachment_urls[] = wp_get_attachment_url( $attachment_id );
622
  }
623
 
624
  foreach ( $maybe_attach as $attachment_url ) {
625
+ if ( ! in_array( $attachment_url, $attachment_urls ) ) {
626
  $this->create_attachment( $attachment_url );
627
  }
628
  }
629
  }
630
 
631
+ // And user meta to save time in future
632
  if ( is_user_logged_in() ) {
633
  update_user_meta( get_current_user_id(), '_company_name', isset( $values['company']['company_name'] ) ? $values['company']['company_name'] : '' );
634
  update_user_meta( get_current_user_id(), '_company_website', isset( $values['company']['company_website'] ) ? $values['company']['company_website'] : '' );
641
  }
642
 
643
  /**
644
+ * Preview Step
645
  */
646
  public function preview() {
647
  global $post, $job_preview;
648
 
649
  if ( $this->job_id ) {
650
  $job_preview = true;
651
+ $post = get_post( $this->job_id );
652
  $post->post_status = 'preview';
653
 
654
  setup_postdata( $post );
655
 
656
+ get_job_manager_template( 'job-preview.php', array(
657
+ 'form' => $this
658
+ ) );
 
 
 
659
 
660
  wp_reset_postdata();
661
  }
662
  }
663
 
664
  /**
665
+ * Preview Step Form handler
666
  */
667
  public function preview_handler() {
668
  if ( ! $_POST ) {
669
  return;
670
  }
671
 
672
+ // Edit = show submit form again
673
  if ( ! empty( $_POST['edit_job'] ) ) {
674
  $this->step --;
675
  }
676
 
677
+ // Continue = change job status then show next screen
678
  if ( ! empty( $_POST['continue'] ) ) {
679
  $job = get_post( $this->job_id );
680
 
681
+ if ( in_array( $job->post_status, array( 'preview', 'expired' ) ) ) {
682
+ // Reset expiry
683
  delete_post_meta( $job->ID, '_job_expires' );
684
 
685
+ // Update job listing
686
  $update_job = array();
687
  $update_job['ID'] = $job->ID;
688
  $update_job['post_status'] = apply_filters( 'submit_job_post_status', get_option( 'job_manager_submission_requires_approval' ) ? 'pending' : 'publish', $job );
698
  }
699
 
700
  /**
701
+ * Done Step
702
  */
703
  public function done() {
704
  do_action( 'job_manager_job_submitted', $this->job_id );
includes/helper/class-wp-job-manager-helper-api.php DELETED
@@ -1,176 +0,0 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) {
4
- exit;
5
- }
6
-
7
- /**
8
- * WP_Job_Manager_Helper_API
9
- */
10
- class WP_Job_Manager_Helper_API {
11
-
12
- const API_BASE_URL = 'https://wpjobmanager.com/';
13
-
14
- /**
15
- * The single instance of the class.
16
- *
17
- * @var self
18
- * @since 1.29.0
19
- */
20
- private static $_instance = null;
21
-
22
- /**
23
- * Allows for accessing single instance of class. Class should only be constructed once per call.
24
- *
25
- * @since 1.29.0
26
- * @static
27
- * @return self Main instance.
28
- */
29
- public static function instance() {
30
- if ( is_null( self::$_instance ) ) {
31
- self::$_instance = new self();
32
- }
33
- return self::$_instance;
34
- }
35
-
36
- /**
37
- * Sends and receives data to and from the server API
38
- *
39
- * @param array|string $args
40
- * @return object|bool $response.
41
- */
42
- public function plugin_update_check( $args ) {
43
- $args = wp_parse_args( $args );
44
- $args['wc-api'] = 'wp_plugin_licencing_update_api';
45
- $args['request'] = 'pluginupdatecheck';
46
- return $this->request( $args );
47
- }
48
-
49
- /**
50
- * Sends and receives data to and from the server API
51
- *
52
- * @param array|string $args
53
- * @return object $response.
54
- */
55
- public function plugin_information( $args ) {
56
- $args = wp_parse_args( $args );
57
- $args['wc-api'] = 'wp_plugin_licencing_update_api';
58
- $args['request'] = 'plugininformation';
59
- return $this->request( $args );
60
- }
61
-
62
- /**
63
- * Attempt to activate a plugin licence.
64
- *
65
- * @param array|string $args
66
- * @return boolean|string JSON response or false if failed.
67
- */
68
- public function activate( $args ) {
69
- $args = wp_parse_args( $args );
70
- $args['wc-api'] = 'wp_plugin_licencing_activation_api';
71
- $args['request'] = 'activate';
72
- $response = $this->request( $args, true );
73
- if ( false === $response ) {
74
- return false;
75
- }
76
- return $response;
77
- }
78
-
79
- /**
80
- * Attempt to deactivate a plugin licence.
81
- *
82
- * @param array|string $args
83
- * @return boolean|string JSON response or false if failed.
84
- */
85
- public function deactivate( $args ) {
86
- $args = wp_parse_args( $args );
87
- $args['wc-api'] = 'wp_plugin_licencing_activation_api';
88
- $args['request'] = 'deactivate';
89
- $response = $this->request( $args, false );
90
- if ( false === $response ) {
91
- return false;
92
- }
93
- return $response;
94
- }
95
-
96
- /**
97
- * Make a licence helper API request.
98
- *
99
- * @param array $args
100
- * @param bool $return_error
101
- *
102
- * @return array|bool|mixed|object
103
- */
104
- protected function request( $args, $return_error = false ) {
105
- $defaults = array(
106
- 'instance' => $this->get_site_url(),
107
- 'plugin_name' => '',
108
- 'version' => '',
109
- 'api_product_id' => '',
110
- 'licence_key' => '',
111
- 'email' => '',
112
- );
113
-
114
- $args = wp_parse_args( $args, $defaults );
115
- $request = wp_safe_remote_get(
116
- $this->get_api_base_url() . '?' . http_build_query( $args, '', '&' ),
117
- array(
118
- 'timeout' => 10,
119
- 'headers' => array(
120
- 'Accept' => 'application/json',
121
- ),
122
- )
123
- );
124
-
125
- if ( is_wp_error( $request ) || 200 !== wp_remote_retrieve_response_code( $request ) ) {
126
- if ( $return_error ) {
127
- if ( is_wp_error( $request ) ) {
128
- return array(
129
- 'error_code' => $request->get_error_code(),
130
- 'error' => $request->get_error_message(),
131
- );
132
- }
133
- return array(
134
- 'error_code' => wp_remote_retrieve_response_code( $request ),
135
- 'error' => 'Error code: ' . wp_remote_retrieve_response_code( $request ),
136
- );
137
- }
138
- return false;
139
- }
140
-
141
- $response = json_decode( wp_remote_retrieve_body( $request ), true );
142
-
143
- if ( is_array( $response ) ) {
144
- return $response;
145
- }
146
-
147
- return false;
148
- }
149
-
150
- /**
151
- * Returns the site URL that is MU safe.
152
- *
153
- * @return string
154
- */
155
- private function get_site_url() {
156
- if ( is_multisite() || is_network_admin() ) {
157
- return network_site_url();
158
- }
159
- return site_url();
160
- }
161
-
162
- /**
163
- * Returns the API base URL.
164
- *
165
- * @return string
166
- */
167
- private function get_api_base_url() {
168
- if ( defined( 'JOB_MANAGER_VERSION' )
169
- && defined( 'JOB_MANAGER_DEV_API_BASE_URL' )
170
- && '-dev' === substr( JOB_MANAGER_VERSION, -4 )
171
- ) {
172
- return JOB_MANAGER_DEV_API_BASE_URL;
173
- }
174
- return self::API_BASE_URL;
175
- }
176
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/helper/class-wp-job-manager-helper-options.php DELETED
@@ -1,115 +0,0 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) {
4
- exit;
5
- }
6
-
7
- /**
8
- * WP_Job_Manager_Helper_Options
9
- */
10
- class WP_Job_Manager_Helper_Options {
11
- const OPTION_NAME = 'job_manager_helper';
12
-
13
- /**
14
- * Update a WPJM plugin's licence data.
15
- *
16
- * @param string $product_slug
17
- * @param string $key
18
- * @param mixed $value
19
- *
20
- * @return bool
21
- */
22
- public static function update( $product_slug, $key, $value ) {
23
- $options = self::get_master_option();
24
- if ( ! isset( $options[ $product_slug ] ) ) {
25
- $options[ $product_slug ] = array();
26
- }
27
- $options[ $product_slug ][ $key ] = $value;
28
- return self::update_master_option( $options );
29
- }
30
-
31
- /**
32
- * Retrieve a WPJM plugin's licence data.
33
- *
34
- * @param string $product_slug
35
- * @param string $key
36
- * @param mixed $default
37
- *
38
- * @return mixed
39
- */
40
- public static function get( $product_slug, $key, $default = false ) {
41
- $options = self::get_master_option();
42
- if ( ! isset( $options[ $product_slug ] ) ) {
43
- $options[ $product_slug ] = self::attempt_legacy_restore( $product_slug );
44
- }
45
- if ( isset( $options[ $product_slug ][ $key ] ) ) {
46
- return $options[ $product_slug ][ $key ];
47
- }
48
- return $default;
49
- }
50
-
51
- /**
52
- * Delete a WPJM plugin's licence data.
53
- *
54
- * @param string $product_slug
55
- * @param string $key
56
- *
57
- * @return bool
58
- */
59
- public static function delete( $product_slug, $key ) {
60
- $options = self::get_master_option();
61
- if ( ! isset( $options[ $product_slug ] ) ) {
62
- $options[ $product_slug ] = array();
63
- }
64
- unset( $options[ $product_slug ][ $key ] );
65
- return self::update_master_option( $options );
66
- }
67
-
68
- /**
69
- * Attempt to retrieve licence data from legacy storage.
70
- *
71
- * @param string $product_slug
72
- *
73
- * @return array
74
- */
75
- private static function attempt_legacy_restore( $product_slug ) {
76
- $options = self::get_master_option();
77
- if ( ! isset( $options[ $product_slug ] ) ) {
78
- $options[ $product_slug ] = array();
79
- }
80
- foreach ( array( 'licence_key', 'email', 'errors', 'hide_key_notice' ) as $key ) {
81
- $option_value = get_option( $product_slug . '_' . $key, false );
82
- if ( ! empty( $option_value ) ) {
83
- $options[ $product_slug ][ $key ] = $option_value;
84
- delete_option( $product_slug . '_' . $key );
85
- }
86
- }
87
- self::update_master_option( $options );
88
- return $options[ $product_slug ];
89
- }
90
-
91
- /**
92
- * Retrieve the master option.
93
- *
94
- * @return array
95
- */
96
- private static function get_master_option() {
97
- if ( is_multisite() || is_network_admin() ) {
98
- return get_site_option( self::OPTION_NAME, array() );
99
- }
100
- return get_option( self::OPTION_NAME, array() );
101
- }
102
-
103
- /**
104
- * Update the master option.
105
- *
106
- * @param array $value Master license container array.
107
- * @return bool
108
- */
109
- private static function update_master_option( $value ) {
110
- if ( is_multisite() || is_network_admin() ) {
111
- return update_site_option( self::OPTION_NAME, $value );
112
- }
113
- return update_option( self::OPTION_NAME, $value );
114
- }
115
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/helper/class-wp-job-manager-helper.php DELETED
@@ -1,609 +0,0 @@
1
- <?php
2
-
3
- if ( ! defined( 'ABSPATH' ) ) {
4
- exit; // Exit if accessed directly.
5
- }
6
-
7
- /**
8
- * Handles Job Manager's Ajax endpoints.
9
- *
10
- * @package wp-job-manager
11
- * @since 1.29.0
12
- */
13
- class WP_Job_Manager_Helper {
14
- /**
15
- * License messages to display to the user.
16
- *
17
- * @var array Messages when updating licences.
18
- */
19
- protected $licence_messages = array();
20
-
21
- /**
22
- * API object.
23
- *
24
- * @var WP_Job_Manager_Helper_API
25
- */
26
- protected $api;
27
-
28
- /**
29
- * The single instance of the class.
30
- *
31
- * @var self
32
- * @since 1.29.0
33
- */
34
- private static $_instance = null;
35
-
36
- /**
37
- * True if the plugin cache has already been cleared.
38
- *
39
- * @var bool
40
- * @since 1.29.1
41
- */
42
- private static $cleared_plugin_cache = false;
43
-
44
- /**
45
- * Allows for accessing single instance of class. Class should only be constructed once per call.
46
- *
47
- * @since 1.29.0
48
- * @static
49
- * @return self Main instance.
50
- */
51
- public static function instance() {
52
- if ( is_null( self::$_instance ) ) {
53
- self::$_instance = new self();
54
- }
55
- return self::$_instance;
56
- }
57
-
58
- /**
59
- * Loads the class, runs on init.
60
- */
61
- public function init() {
62
- include_once dirname( __FILE__ ) . '/class-wp-job-manager-helper-options.php';
63
- include_once dirname( __FILE__ ) . '/class-wp-job-manager-helper-api.php';
64
-
65
- $this->api = WP_Job_Manager_Helper_API::instance();
66
-
67
- add_action( 'job_manager_helper_output', array( $this, 'licence_output' ) );
68
-
69
- add_filter( 'job_manager_addon_core_version_check', array( $this, 'addon_core_version_check' ), 10, 2 );
70
- add_filter( 'extra_plugin_headers', array( $this, 'extra_headers' ) );
71
- add_filter( 'plugins_api', array( $this, 'plugins_api' ), 20, 3 );
72
- add_action( 'pre_set_site_transient_update_plugins', array( $this, 'check_for_updates' ) );
73
-
74
- add_action( 'activated_plugin', array( $this, 'plugin_activated' ) );
75
- add_action( 'deactivated_plugin', array( $this, 'plugin_deactivated' ) );
76
- add_action( 'admin_init', array( $this, 'admin_init' ) );
77
- }
78
-
79
- /**
80
- * Initializes admin-only actions.
81
- */
82
- public function admin_init() {
83
- add_action( 'plugin_action_links', array( $this, 'plugin_links' ), 10, 2 );
84
- add_action( 'admin_notices', array( $this, 'licence_error_notices' ) );
85
- $this->handle_admin_request();
86
- }
87
-
88
- /**
89
- * Handles special tasks on admin requests.
90
- */
91
- private function handle_admin_request() {
92
- if ( ! empty( $_GET['dismiss-wpjm-licence-notice'] ) ) {
93
- $product_plugins = $this->get_installed_plugins();
94
- $product_slug = sanitize_text_field( $_GET['dismiss-wpjm-licence-notice'] );
95
- if ( isset( $product_plugins[ $product_slug ] ) ) {
96
- WP_Job_Manager_Helper_Options::update( $product_slug, 'hide_key_notice', true );
97
- }
98
- }
99
- }
100
-
101
- /**
102
- * Tell the add-on when to check for and display and core WPJM version notices.
103
- *
104
- * @param bool $do_check True if the add-on should do a core version check.
105
- * @param string $minimum_required_core_version Minimum version the plugin is reporting it requires.
106
- * @return bool
107
- */
108
- public function addon_core_version_check( $do_check, $minimum_required_core_version = null ) {
109
- if ( ! is_admin() || ! did_action( 'admin_init' ) ) {
110
- return false;
111
- }
112
-
113
- // We only want to show the notices on the plugins page and main job listing admin page.
114
- $screen = get_current_screen();
115
- if ( null === $screen || ! in_array( $screen->id, array( 'plugins', 'edit-job_listing' ), true ) ) {
116
- return false;
117
- }
118
-
119
- $dev_version_loc = strpos( JOB_MANAGER_VERSION, '-dev' );
120
- if (
121
- false !== $dev_version_loc
122
- && substr( JOB_MANAGER_VERSION, 0, $dev_version_loc ) === $minimum_required_core_version
123
- ) {
124
- return false;
125
- }
126
-
127
- return $do_check;
128
- }
129
-
130
- /**
131
- * Check for licence managed WPJM addon plugin updates.
132
- *
133
- * @param array $check_for_updates_data
134
- *
135
- * @return array
136
- */
137
- public function check_for_updates( $check_for_updates_data ) {
138
- // Set version variables.
139
- foreach ( $this->get_installed_plugins() as $product_slug => $plugin_data ) {
140
- $response = $this->get_plugin_version( $plugin_data['_filename'] );
141
- // If there is a new version, modify the transient to reflect an update is available.
142
- if ( $response
143
- && isset( $response['new_version'] )
144
- && version_compare( $response['new_version'], $plugin_data['Version'], '>' )
145
- ) {
146
- $check_for_updates_data->response[ $plugin_data['_filename'] ] = (object) $response;
147
- }
148
- }
149
- return $check_for_updates_data;
150
- }
151
-
152
- /**
153
- * Get plugin version info from API.
154
- *
155
- * @param string $plugin_filename
156
- *
157
- * @return array|bool
158
- */
159
- private function get_plugin_version( $plugin_filename ) {
160
- $plugin_data = $this->get_licence_managed_plugin( $plugin_filename );
161
- if ( ! $plugin_data ) {
162
- return false;
163
- }
164
- $product_slug = $plugin_data['_product_slug'];
165
- $licence = $this->get_plugin_licence( $product_slug );
166
- if ( ! $licence || empty( $licence['licence_key'] ) ) {
167
- return false;
168
- }
169
-
170
- $response = $this->api->plugin_update_check(
171
- array(
172
- 'plugin_name' => $plugin_data['Name'],
173
- 'version' => $plugin_data['Version'],
174
- 'api_product_id' => $product_slug,
175
- 'licence_key' => $licence['licence_key'],
176
- 'email' => $licence['email'],
177
- )
178
- );
179
-
180
- $this->handle_api_errors( $product_slug, $response );
181
-
182
- // Set version variables.
183
- if ( ! empty( $response ) ) {
184
- return $response;
185
- }
186
-
187
- return false;
188
- }
189
-
190
- /**
191
- * Cleanup old things when WPJM licence managed plugin is activated.
192
- *
193
- * @param string $plugin_filename
194
- */
195
- public function plugin_activated( $plugin_filename ) {
196
- $plugins = $this->get_installed_plugins( false );
197
- foreach ( $plugins as $product_slug => $plugin_data ) {
198
- if ( $plugin_filename !== $plugin_data['_filename'] ) {
199
- continue;
200
- }
201
- WP_Job_Manager_Helper_Options::delete( $product_slug, 'hide_key_notice' );
202
- break;
203
- }
204
- }
205
-
206
- /**
207
- * Deactivate licence when WPJM licence managed plugin is deactivated.
208
- *
209
- * @param string $plugin_filename
210
- */
211
- public function plugin_deactivated( $plugin_filename ) {
212
- $plugins = $this->get_installed_plugins( false );
213
- foreach ( $plugins as $product_slug => $plugin_data ) {
214
- if ( $plugin_filename !== $plugin_data['_filename'] ) {
215
- continue;
216
- }
217
- $this->deactivate_licence( $product_slug );
218
- break;
219
- }
220
- }
221
-
222
- /**
223
- * Fetches the plugin information for WPJM plugins.
224
- *
225
- * @param false|object|array $response The result object or array. Default false.
226
- * @param string $action The type of information being requested from the Plugin Install API.
227
- * @param object $args Plugin API arguments.
228
- *
229
- * @return false|object|array
230
- */
231
- public function plugins_api( $response, $action, $args ) {
232
- if ( 'plugin_information' !== $action ) {
233
- return $response;
234
- }
235
-
236
- if ( empty( $args->slug ) ) {
237
- return $response;
238
- }
239
-
240
- $plugin_info = $this->get_plugin_info( $args->slug );
241
- if ( $plugin_info ) {
242
- $response = (object) $plugin_info;
243
- }
244
-
245
- return $response;
246
- }
247
-
248
- /**
249
- * Appends links to manage plugin licence when managed.
250
- *
251
- * @param array $actions
252
- * @param string $plugin_filename
253
- * @return array
254
- */
255
- public function plugin_links( $actions, $plugin_filename ) {
256
- $plugin = $this->get_licence_managed_plugin( $plugin_filename );
257
- if ( ! $plugin || ! current_user_can( 'update_plugins' ) ) {
258
- return $actions;
259
- }
260
- $product_slug = $plugin['_product_slug'];
261
- $licence = $this->get_plugin_licence( $product_slug );
262
- $css_class = '';
263
- if ( $licence && ! empty( $licence['licence_key'] ) ) {
264
- if ( ! empty( $licence['errors'] ) ) {
265
- $manage_licence_label = __( 'Manage License (Requires Attention)', 'wp-job-manager' );
266
- $css_class = 'wpjm-activate-licence-link';
267
- } else {
268
- $manage_licence_label = __( 'Manage License', 'wp-job-manager' );
269
- }
270
- } else {
271
- $manage_licence_label = __( 'Activate License', 'wp-job-manager' );
272
- $css_class = 'wpjm-activate-licence-link';
273
- }
274
- $actions[] = '<a class="' . esc_attr( $css_class ) . '" href="' . esc_url( admin_url( 'edit.php?post_type=job_listing&page=job-manager-addons&section=helper' ) ) . '">' . esc_html( $manage_licence_label ) . '</a>';
275
- return $actions;
276
- }
277
-
278
- /**
279
- * Returns the plugin info for a licensed WPJM plugin.
280
- *
281
- * @param string $product_slug
282
- *
283
- * @return bool|object
284
- */
285
- protected function get_plugin_info( $product_slug ) {
286
- if ( ! $this->is_product_installed( $product_slug ) ) {
287
- return false;
288
- }
289
- $args = $this->get_plugin_licence( $product_slug );
290
- if ( empty( $args['licence_key'] ) || empty( $args['email'] ) ) {
291
- return false;
292
- }
293
- $args['api_product_id'] = $product_slug;
294
-
295
- $response = $this->api->plugin_information( $args );
296
- $this->handle_api_errors( $product_slug, $response );
297
-
298
- return $response;
299
- }
300
-
301
- /**
302
- * Checks if a WPJM plugin is installed.
303
- *
304
- * @param string $product_slug
305
- *
306
- * @return bool
307
- */
308
- public function is_product_installed( $product_slug ) {
309
- $product_plugins = $this->get_installed_plugins();
310
- return isset( $product_plugins[ $product_slug ] );
311
- }
312
-
313
- /**
314
- * Returns true if there are licensed products being managed.
315
- *
316
- * @return bool
317
- */
318
- public function has_licenced_products() {
319
- $product_plugins = $this->get_installed_plugins();
320
- return ! empty( $product_plugins );
321
- }
322
-
323
- /**
324
- * Returns the plugin data for plugin with a `WPJM-Product` tag by plugin filename.
325
- *
326
- * @param string $plugin_filename
327
- * @return bool|array
328
- */
329
- private function get_licence_managed_plugin( $plugin_filename ) {
330
- foreach ( $this->get_installed_plugins() as $plugin ) {
331
- if ( $plugin_filename === $plugin['_filename'] ) {
332
- return $plugin;
333
- }
334
- }
335
- return false;
336
- }
337
-
338
- /**
339
- * Gets the license key and email for a WPJM managed plugin.
340
- *
341
- * @param string $product_slug
342
- * @return array|bool
343
- */
344
- public function get_plugin_licence( $product_slug ) {
345
- $licence_key = WP_Job_Manager_Helper_Options::get( $product_slug, 'licence_key' );
346
- $activation_email = WP_Job_Manager_Helper_Options::get( $product_slug, 'email' );
347
- $errors = WP_Job_Manager_Helper_Options::get( $product_slug, 'errors' );
348
-
349
- return array(
350
- 'licence_key' => $licence_key,
351
- 'email' => $activation_email,
352
- 'errors' => $errors,
353
- );
354
- }
355
-
356
- /**
357
- * Adds newly recognized data header in WordPress plugin files.
358
- *
359
- * @param array $headers
360
- * @return array
361
- */
362
- public function extra_headers( $headers ) {
363
- $headers[] = 'WPJM-Product';
364
- return $headers;
365
- }
366
-
367
- /**
368
- * Returns list of installed WPJM plugins with managed licenses indexed by product ID.
369
- *
370
- * @param bool $active_only Only return active plugins.
371
- * @return array
372
- */
373
- protected function get_installed_plugins( $active_only = true ) {
374
- if ( ! function_exists( 'get_plugins' ) ) {
375
- require_once ABSPATH . 'wp-admin/includes/plugin.php';
376
- }
377
-
378
- /**
379
- * Clear the plugin cache on first request for installed WPJM add-on plugins.
380
- *
381
- * @since 1.29.1
382
- *
383
- * @param bool $clear_plugin_cache True if we should clear the plugin cache.
384
- */
385
- if ( ! self::$cleared_plugin_cache && apply_filters( 'job_manager_clear_plugin_cache', true ) ) {
386
- // Reset the plugin cache on the first call. Some plugins prematurely hydrate the cache.
387
- wp_clean_plugins_cache( false );
388
- self::$cleared_plugin_cache = true;
389
- }
390
-
391
- $wpjm_plugins = array();
392
- $plugins = get_plugins();
393
-
394
- foreach ( $plugins as $filename => $data ) {
395
- if ( empty( $data['WPJM-Product'] ) || ( true === $active_only && ! is_plugin_active( $filename ) ) ) {
396
- continue;
397
- }
398
-
399
- $data['_filename'] = $filename;
400
- $data['_product_slug'] = $data['WPJM-Product'];
401
- $data['_type'] = 'plugin';
402
- $wpjm_plugins[ $data['WPJM-Product'] ] = $data;
403
- }
404
-
405
- return $wpjm_plugins;
406
- }
407
-
408
- /**
409
- * Outputs the license management.
410
- */
411
- public function licence_output() {
412
- if ( ! current_user_can( 'update_plugins' ) ) {
413
- return;
414
- }
415
- if ( ! empty( $_POST ) ) {
416
- $this->handle_request();
417
- }
418
- $licenced_plugins = $this->get_installed_plugins();
419
- include_once dirname( __FILE__ ) . '/views/html-licences.php';
420
- }
421
-
422
- /**
423
- * Outputs unset license key notices.
424
- */
425
- public function licence_error_notices() {
426
- $screen = get_current_screen();
427
- if ( null === $screen || in_array( $screen->id, array( 'job_listing_page_job-manager-addons' ), true ) ) {
428
- return;
429
- }
430
- foreach ( $this->get_installed_plugins() as $product_slug => $plugin_data ) {
431
- $licence = $this->get_plugin_licence( $product_slug );
432
- if ( ! WP_Job_Manager_Helper_Options::get( $product_slug, 'hide_key_notice' ) ) {
433
- if ( empty( $licence['licence_key'] ) ) {
434
- include 'views/html-licence-key-notice.php';
435
- } elseif ( ! empty( $licence['errors'] ) ) {
436
- include 'views/html-licence-key-error.php';
437
- }
438
- }
439
- }
440
- }
441
-
442
- /**
443
- * Handles a request on the manage license key screen.
444
- */
445
- private function handle_request() {
446
- $licenced_plugins = $this->get_installed_plugins();
447
- if ( empty( $_POST )
448
- || empty( $_POST['_wpnonce'] )
449
- || empty( $_POST['action'] )
450
- || empty( $_POST['product_slug'] )
451
- || ! isset( $licenced_plugins[ $_POST['product_slug'] ] )
452
- || ! wp_verify_nonce( $_POST['_wpnonce'], 'wpjm-manage-licence' )
453
- ) {
454
- return false;
455
- }
456
- $product_slug = sanitize_text_field( $_POST['product_slug'] );
457
- switch ( $_POST['action'] ) {
458
- case 'activate':
459
- if ( empty( $_POST['email'] ) || empty( $_POST['licence_key'] ) ) {
460
- $this->add_error( $product_slug, __( 'Please enter a valid license key and email address in order to activate this plugin\'s license.', 'wp-job-manager' ) );
461
- break;
462
- }
463
- $email = sanitize_email( $_POST['email'] );
464
- $licence_key = sanitize_text_field( $_POST['licence_key'] );
465
- $this->activate_licence( $product_slug, $licence_key, $email );
466
- break;
467
- case 'deactivate':
468
- $this->deactivate_licence( $product_slug );
469
- break;
470
- }
471
- }
472
-
473
- /**
474
- * Activate a licence key for a WPJM add-on plugin.
475
- *
476
- * @param string $product_slug
477
- * @param string $licence_key
478
- * @param string $email
479
- */
480
- private function activate_licence( $product_slug, $licence_key, $email ) {
481
- $response = $this->api->activate(
482
- array(
483
- 'api_product_id' => $product_slug,
484
- 'licence_key' => $licence_key,
485
- 'email' => $email,
486
- )
487
- );
488
-
489
- if ( false === $response ) {
490
- $this->add_error( $product_slug, __( 'Connection failed to the License Key API server - possible server issue.', 'wp-job-manager' ) );
491
- } elseif ( isset( $response['error_code'] ) && isset( $response['error'] ) ) {
492
- $this->add_error( $product_slug, $response['error'] );
493
- } elseif ( ! empty( $response['activated'] ) ) {
494
- WP_Job_Manager_Helper_Options::update( $product_slug, 'licence_key', $licence_key );
495
- WP_Job_Manager_Helper_Options::update( $product_slug, 'email', $email );
496
- WP_Job_Manager_Helper_Options::delete( $product_slug, 'errors' );
497
- WP_Job_Manager_Helper_Options::delete( $product_slug, 'hide_key_notice' );
498
- $this->add_success( $product_slug, __( 'Plugin license has been activated.', 'wp-job-manager' ) );
499
- } else {
500
- $this->add_error( $product_slug, __( 'An unknown error occurred while attempting to activate the license', 'wp-job-manager' ) );
501
- }
502
- }
503
-
504
- /**
505
- * Deactivate a licence key for a WPJM add-on plugin.
506
- *
507
- * @param string $product_slug
508
- */
509
- private function deactivate_licence( $product_slug ) {
510
- $licence = $this->get_plugin_licence( $product_slug );
511
- if ( empty( $licence['licence_key'] ) || empty( $licence['email'] ) ) {
512
- $this->add_error( $product_slug, __( 'license is not active.', 'wp-job-manager' ) );
513
- return;
514
- }
515
- $this->api->deactivate(
516
- array(
517
- 'api_product_id' => $product_slug,
518
- 'licence_key' => $licence['licence_key'],
519
- 'email' => $licence['email'],
520
- )
521
- );
522
-
523
- WP_Job_Manager_Helper_Options::delete( $product_slug, 'licence_key' );
524
- WP_Job_Manager_Helper_Options::delete( $product_slug, 'email' );
525
- WP_Job_Manager_Helper_Options::delete( $product_slug, 'errors' );
526
- WP_Job_Manager_Helper_Options::delete( $product_slug, 'hide_key_notice' );
527
- delete_site_transient( 'update_plugins' );
528
- $this->add_success( $product_slug, __( 'Plugin license has been deactivated.', 'wp-job-manager' ) );
529
- }
530
-
531
- /**
532
- * Handle errors from the API.
533
- *
534
- * @param string $product_slug
535
- * @param array $response
536
- */
537
- private function handle_api_errors( $product_slug, $response ) {
538
- $plugin_products = $this->get_installed_plugins();
539
- if ( ! isset( $plugin_products[ $product_slug ] ) ) {
540
- return;
541
- }
542
-
543
- $errors = ! empty( $response['errors'] ) ? $response['errors'] : array();
544
- $allowed_errors = array( 'no_activation', 'expired_key', 'expiring_soon' );
545
- $ignored_errors = array_diff( array_keys( $errors ), $allowed_errors );
546
-
547
- foreach ( $ignored_errors as $key ) {
548
- unset( $errors[ $key ] );
549
- }
550
-
551
- if ( ! empty( $errors['no_activation'] ) ) {
552
- $this->deactivate_licence( $product_slug );
553
- }
554
-
555
- WP_Job_Manager_Helper_Options::update( $product_slug, 'errors', $errors );
556
- }
557
-
558
- /**
559
- * Add an error message.
560
- *
561
- * @param string $product_slug The plugin slug.
562
- * @param string $message Your error message.
563
- */
564
- private function add_error( $product_slug, $message ) {
565
- $this->add_message( 'error', $product_slug, $message );
566
- }
567
-
568
- /**
569
- * Add a success message.
570
- *
571
- * @param string $product_slug The plugin slug.
572
- * @param string $message Your error message.
573
- */
574
- private function add_success( $product_slug, $message ) {
575
- $this->add_message( 'success', $product_slug, $message );
576
- }
577
-
578
- /**
579
- * Add a message.
580
- *
581
- * @param string $type Message type.
582
- * @param string $product_slug The plugin slug.
583
- * @param string $message Your error message.
584
- */
585
- private function add_message( $type, $product_slug, $message ) {
586
- if ( ! isset( $this->licence_messages[ $product_slug ] ) ) {
587
- $this->licence_messages[ $product_slug ] = array();
588
- }
589
- $this->licence_messages[ $product_slug ][] = array(
590
- 'type' => $type,
591
- 'message' => $message,
592
- );
593
- }
594
-
595
- /**
596
- * Get a plugin's licence messages.
597
- *
598
- * @param string $product_slug The plugin slug.
599
- * @return array
600
- */
601
- public function get_messages( $product_slug ) {
602
- if ( ! isset( $this->licence_messages[ $product_slug ] ) ) {
603
- $this->licence_messages[ $product_slug ] = array();
604
- }
605
- return $this->licence_messages[ $product_slug ];
606
- }
607
- }
608
-
609
- WP_Job_Manager_Helper::instance()->init();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/helper/views/html-licence-key-error.php DELETED
@@ -1,9 +0,0 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) {
3
- exit; // Exit if accessed directly.
4
- }
5
- ?>
6
- <div class="error">
7
- <p class="wpjm-updater-dismiss" style="float:right;"><a href="<?php echo esc_url( add_query_arg( 'dismiss-wpjm-licence-notice', $product_slug ) ); ?>"><?php esc_html_e( 'Hide notice', 'wp-job-manager' ); ?></a></p>
8
- <p><?php printf( 'There is a problem with the license for "%s". Please <a href="%s">manage the license</a> to check for a solution and continue receiving updates.', esc_html( $plugin_data['Name'] ), esc_url( admin_url( 'edit.php?post_type=job_listing&page=job-manager-addons&section=helper#' . sanitize_title( $product_slug . '_row' ) ) ) ); ?></p>
9
- </div>
 
 
 
 
 
 
 
 
 
includes/helper/views/html-licence-key-notice.php DELETED
@@ -1,9 +0,0 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) {
3
- exit; // Exit if accessed directly.
4
- }
5
- ?>
6
- <div class="updated">
7
- <p class="wpjm-updater-dismiss" style="float:right;"><a href="<?php echo esc_url( add_query_arg( 'dismiss-wpjm-licence-notice', $product_slug ) ); ?>"><?php esc_html_e( 'Hide notice', 'wp-job-manager' ); ?></a></p>
8
- <p><?php printf( '<a href="%s">Please enter your license key</a> to get updates for "%s".', esc_url( admin_url( 'edit.php?post_type=job_listing&page=job-manager-addons&section=helper#' . sanitize_title( $product_slug . '_row' ) ) ), esc_html( $plugin_data['Name'] ) ); ?></p>
9
- </div>
 
 
 
 
 
 
 
 
 
includes/helper/views/html-licences.php DELETED
@@ -1,81 +0,0 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) {
3
- exit; // Exit if accessed directly.
4
- }
5
- ?>
6
- <h1 class="screen-reader-text"><?php esc_html_e( 'Licenses', 'wp-job-manager' ); ?></h1>
7
- <div class="wpjm-licences">
8
- <?php if ( ! empty( $licenced_plugins ) ) : ?>
9
- <?php foreach ( $licenced_plugins as $product_slug => $plugin_data ) : ?>
10
- <?php
11
- $licence = WP_Job_Manager_Helper::get_plugin_licence( $product_slug );
12
- ?>
13
- <div class="licence-row">
14
- <div class="plugin-info">
15
- <?php echo esc_html( $plugin_data['Name'] ); ?>
16
- <div class="plugin-author">
17
- <?php
18
- $author = $plugin_data['Author'];
19
- if ( ! empty( $plugin_data['AuthorURI'] ) ) {
20
- $author = '<a href="' . esc_url( $plugin_data['AuthorURI'] ) . '">' . wp_kses_post( $plugin_data['Author'] ) . '</a>';
21
- }
22
- echo wp_kses_post( $author );
23
- ?>
24
- </div>
25
- </div>
26
- <div class="plugin-licence">
27
- <?php
28
- $notices = WP_Job_Manager_Helper::get_messages( $product_slug );
29
- if ( empty( $notices ) && ! empty( $licence['errors'] ) ) {
30
- $notices = array();
31
- foreach ( $licence['errors'] as $key => $error ) {
32
- $notices[] = array(
33
- 'type' => 'error',
34
- 'message' => $error,
35
- );
36
- }
37
- }
38
- foreach ( $notices as $message ) {
39
- echo '<div class="notice inline notice-' . esc_attr( $message['type'] ) . '"><p>' . wp_kses_post( $message['message'] ) . '</p></div>';
40
- }
41
- ?>
42
- <form method="post">
43
- <?php wp_nonce_field( 'wpjm-manage-licence' ); ?>
44
- <?php
45
- if ( ! empty( $licence['licence_key'] ) && ! empty( $licence['email'] ) ) {
46
- ?>
47
- <input type="hidden" id="<?php echo esc_attr( sanitize_title( $product_slug ) ); ?>_action" name="action" value="deactivate"/>
48
- <input type="hidden" id="<?php echo esc_attr( sanitize_title( $product_slug ) ); ?>_plugin" name="product_slug" value="<?php echo esc_attr( $product_slug ); ?>"/>
49
-
50
- <label for="<?php echo esc_attr( sanitize_title( $product_slug ) ); ?>_licence_key"><?php esc_html_e( 'License', 'wp-job-manager' ); ?>:
51
- <input type="text" disabled="disabled" id="<?php echo esc_attr( sanitize_title( $product_slug ) ); ?>_licence_key" name="licence_key" placeholder="XXXX-XXXX-XXXX-XXXX" value="<?php echo esc_attr( $licence['licence_key'] ); ?>"/>
52
- </label>
53
- <label for="<?php echo esc_attr( sanitize_title( $product_slug ) ); ?>_email"><?php esc_html_e( 'Email', 'wp-job-manager' ); ?>:
54
- <input type="email" disabled="disabled" id="<?php echo esc_attr( sanitize_title( $product_slug ) ); ?>_email" name="email" placeholder="Email address" value="<?php echo esc_attr( $licence['email'] ); ?>"/>
55
- </label>
56
-
57
- <input type="submit" class="button" name="submit" value="<?php esc_attr_e( 'Deactivate License', 'wp-job-manager' ); ?>" />
58
- <?php
59
- } else { // licence is not active.
60
- ?>
61
- <input type="hidden" id="<?php echo esc_attr( sanitize_title( $product_slug ) ); ?>_action" name="action" value="activate"/>
62
- <input type="hidden" id="<?php echo esc_attr( sanitize_title( $product_slug ) ); ?>_plugin" name="product_slug" value="<?php echo esc_attr( $product_slug ); ?>"/>
63
- <label for="<?php echo esc_attr( sanitize_title( $product_slug ) ); ?>_licence_key"><?php esc_html_e( 'License', 'wp-job-manager' ); ?>:
64
- <input type="text" id="<?php echo esc_attr( sanitize_title( $product_slug ) ); ?>_licence_key" name="licence_key" placeholder="XXXX-XXXX-XXXX-XXXX"/>
65
- </label>
66
- <label for="<?php echo esc_attr( sanitize_title( $product_slug ) ); ?>_email"><?php esc_html_e( 'Email', 'wp-job-manager' ); ?>:
67
- <input type="email" id="<?php echo esc_attr( sanitize_title( $product_slug ) ); ?>_email" name="email" placeholder="Email address" value="<?php echo esc_attr( get_option( 'admin_email' ) ); ?>"/>
68
- </label>
69
- <input type="submit" class="button" name="submit" value="<?php esc_attr_e( 'Activate License', 'wp-job-manager' ); ?>" />
70
- <?php
71
- } // end if : else licence is not active.
72
- ?>
73
- </form>
74
- </div>
75
- </div>
76
- <?php endforeach; ?>
77
- <div class="notice notice-info inline"><p><?php printf( 'Lost your license key? <a href="%s">Retrieve it here</a>.', 'https://wpjobmanager.com/lost-licence-key/' ); ?></p></div>
78
- <?php else : ?>
79
- <div class="notice notice-warning inline"><p><?php esc_html_e( 'No plugins are activated that have licenses managed by WP Job Manager.', 'wp-job-manager' ); ?></p></div>
80
- <?php endif; ?>
81
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/rest-api/class-wp-job-manager-controllers-status.php DELETED
@@ -1,132 +0,0 @@
1
- <?php
2
- /**
3
- * Declaration of our Status Model
4
- *
5
- * @package WPJM/REST
6
- */
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit;
10
- }
11
-
12
- /**
13
- * Class WP_Job_Manager_Controllers_Status
14
- */
15
- class WP_Job_Manager_Controllers_Status extends WP_Job_Manager_REST_Controller_Model
16
- implements WP_Job_Manager_REST_Interfaces_Controller {
17
-
18
-
19
- /**
20
- * Setup
21
- */
22
- public function setup() {
23
- $this->add_route( '/' )
24
- ->add_action( $this->action( 'index', 'index' ) );
25
-
26
- $this->add_route( '/(?P<key>[a-zA-Z_]+)' )
27
- ->add_action( $this->action( 'show', 'show' ) )
28
- ->add_action( $this->action( 'update', 'update' ) );
29
- }
30
-
31
- /**
32
- * Index handler
33
- *
34
- * @param WP_REST_Request $request The request.
35
- *
36
- * @return WP_REST_Response
37
- * @throws WP_Job_Manager_REST_Exception Thrown during error while processing of request.
38
- */
39
- public function index( $request ) {
40
- $params = $request->get_params();
41
- $filter = $this->environment()
42
- ->model( 'WP_Job_Manager_Filters_Status' )
43
- ->new_from_array( $params );
44
-
45
- if ( is_wp_error( $filter ) ) {
46
- return $this->bad_request( $filter );
47
- }
48
-
49
- $configuration = $this->get_model_prototype()
50
- ->get_data_store()
51
- ->get_entity( null );
52
-
53
- if ( empty( $configuration ) ) {
54
- return $this->not_found( __( 'Not Found', 'wp-job-manager' ) );
55
- }
56
-
57
- $dto = $this->prepare_dto( $configuration );
58
- $keys = $filter->get( 'keys' );
59
- if ( empty( $keys ) ) {
60
- return $this->ok( $dto );
61
- }
62
-
63
- $filtered_params = array();
64
- foreach ( $keys as $key ) {
65
- if ( isset( $params[ $key ] ) ) {
66
- $filtered_params[ $key ] = $params[ $key ];
67
- }
68
- }
69
- return $this->ok( $filtered_params );
70
- }
71
-
72
- /**
73
- * Show handler
74
- *
75
- * @param WP_REST_Request $request The request.
76
- * @return WP_REST_Response
77
- */
78
- public function show( $request ) {
79
- $key = $request->get_param( 'key' );
80
- $configuration = $this->get_model_prototype()
81
- ->get_data_store()
82
- ->get_entity( null );
83
-
84
- if ( ! $configuration->has( $key ) ) {
85
- return $this->not_found( 'Invalid key: ' . $key );
86
- }
87
-
88
- return $configuration->get( $key );
89
- }
90
-
91
- /**
92
- * Update handler
93
- *
94
- * @param WP_REST_Request $request The request.
95
- * @return WP_REST_Response
96
- */
97
- public function update( $request ) {
98
- $key = $request->get_param( 'key' );
99
- $value = $request->get_param( 'value' );
100
- if ( ! isset( $value ) ) {
101
- if ( ! function_exists( 'json_decode' ) ) {
102
- include_once ABSPATH . WPINC . 'compat.php';
103
- }
104
- $body = $request->get_body();
105
- $value = json_decode( $body, true );
106
- }
107
- $thing_to_update = array(
108
- $key => $value,
109
- );
110
-
111
- $configuration = $this->get_model_prototype()
112
- ->get_data_store()
113
- ->get_entity( '' );
114
- $configuration->update_from_array( $thing_to_update );
115
- $result = $this->get_model_prototype()
116
- ->get_data_store()
117
- ->upsert( $configuration );
118
-
119
- if ( is_wp_error( $result ) ) {
120
- return $this->respond( $result, 500 );
121
- }
122
-
123
- $dto = $this->prepare_dto( $configuration );
124
-
125
- if ( WP_REST_Server::CREATABLE === $request->get_method() ) {
126
- return $this->created( $dto );
127
- }
128
-
129
- return $this->ok( $dto );
130
- }
131
- }
132
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/rest-api/class-wp-job-manager-data-stores-status.php DELETED
@@ -1,72 +0,0 @@
1
- <?php
2
- /**
3
- * Declaration of our Status Data Store
4
- *
5
- * @package WPJM/REST
6
- */
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit;
10
- }
11
-
12
- /**
13
- * Class WP_Job_Manager_Data_Stores_Status
14
- */
15
- class WP_Job_Manager_Data_Stores_Status extends WP_Job_Manager_REST_Data_Store_Abstract
16
- implements WP_Job_Manager_REST_Interfaces_Data_Store {
17
-
18
- /**
19
- * Get all the models (taking into account any filtering)
20
- *
21
- * @param WP_Job_Manager_REST_Interfaces_Model|null $filter A filter.
22
- * @return WP_Job_Manager_REST_Model_Collection
23
- * @throws WP_Job_Manager_REST_Exception Thrown during error while processing of request.
24
- */
25
- public function get_entities( $filter = null ) {
26
- return new WP_Job_Manager_REST_Model_Collection( array( $this->get_entity( null ) ) );
27
- }
28
-
29
- /**
30
- * Get a Model Using it's unique identifier
31
- *
32
- * @param mixed $id The id of the entity.
33
- *
34
- * @return WP_Job_Manager_REST_Interfaces_Model
35
- * @throws WP_Job_Manager_REST_Exception Thrown during error while processing of request.
36
- */
37
- public function get_entity( $id ) {
38
- $should_run_page_setup = (bool) get_transient( '_job_manager_activation_redirect' );
39
- $params = array(
40
- 'run_page_setup' => $should_run_page_setup,
41
- );
42
- return $this->get_model_prototype()->create( $params );
43
- }
44
-
45
- /**
46
- * Delete a Model
47
- *
48
- * @param WP_Job_Manager_REST_Interfaces_Model $model The model to delete.
49
- * @param array $args Args.
50
- * @return mixed
51
- */
52
- public function delete( $model, $args = array() ) {
53
- return true;
54
- }
55
-
56
- /**
57
- * Update/Insert Model
58
- *
59
- * @param WP_Job_Manager_REST_Interfaces_Model $model The model.
60
- * @return mixed
61
- */
62
- public function upsert( $model ) {
63
- $run_page_setup_val = $model->get( 'run_page_setup' );
64
- if ( $run_page_setup_val ) {
65
- set_transient( '_job_manager_activation_redirect', 1, HOUR_IN_SECONDS );
66
- } else {
67
- delete_transient( '_job_manager_activation_redirect' );
68
- }
69
- return true;
70
- }
71
- }
72
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/rest-api/class-wp-job-manager-filters-status.php DELETED
@@ -1,46 +0,0 @@
1
- <?php
2
- /**
3
- * Declaration of our Status Filters (will be used in GET requests)
4
- *
5
- * @package WPJM/REST
6
- */
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit;
10
- }
11
-
12
- /**
13
- * Class WP_Job_Manager_Filters_Status
14
- */
15
- class WP_Job_Manager_Filters_Status extends WP_Job_Manager_REST_Model {
16
-
17
- /**
18
- * Declare our fields
19
- *
20
- * @return array
21
- * @throws WP_Job_Manager_REST_Exception Exc.
22
- */
23
- public function declare_fields() {
24
- $env = $this->get_environment();
25
- return array(
26
- $env->field( 'keys', 'The status keys to return' )
27
- ->with_type( $env->type( 'array:string' ) )
28
- ->with_before_set( 'explode_keys' )
29
- ->with_default( array() ),
30
- );
31
- }
32
-
33
- /**
34
- * Explode keys
35
- *
36
- * @param mixed $keys The keys.
37
- * @return array
38
- */
39
- public function explode_keys( $keys ) {
40
- if ( is_string( $keys ) ) {
41
- return explode( ',', $keys );
42
- }
43
- return $keys;
44
- }
45
- }
46
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/rest-api/class-wp-job-manager-models-job-categories-custom-fields.php DELETED
@@ -1,26 +0,0 @@
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-job-listings-custom-fields.php DELETED
@@ -1,104 +0,0 @@
1
- <?php
2
- /**
3
- * Declaration of our Job Listings 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_Listings_Custom_Fields
14
- */
15
- class WP_Job_Manager_Models_Job_Listings_Custom_Fields extends WP_Job_Manager_REST_Model {
16
-
17
- /**
18
- * Declare Fields
19
- *
20
- * @return array
21
- * @throws WP_Job_Manager_REST_Exception Exc.
22
- */
23
- public function declare_fields() {
24
- $env = $this->get_environment();
25
- $current_user = wp_get_current_user();
26
-
27
- $declarations = array(
28
- $env->field( '_job_location', __( 'Leave this blank if the location is not important.', 'wp-job-manager' ) )
29
- ->with_kind( WP_Job_Manager_REST_Field_Declaration::META )
30
- ->with_type( $env->type( 'string' ) ),
31
-
32
- $env->field( '_application', __( 'Application Email or URL', 'wp-job-manager' ) )
33
- ->with_kind( WP_Job_Manager_REST_Field_Declaration::META )
34
- ->with_default( $current_user->user_email )
35
- ->with_type( $env->type( 'string' ) ),
36
-
37
- $env->field( '_company_name', __( 'Company Name', 'wp-job-manager' ) )
38
- ->with_kind( WP_Job_Manager_REST_Field_Declaration::META )
39
- ->with_type( $env->type( 'string' ) ),
40
-
41
- $env->field( '_company_website', __( 'Company Website', 'wp-job-manager' ) )
42
- ->with_kind( WP_Job_Manager_REST_Field_Declaration::META )
43
- ->with_type( $env->type( 'string' ) ),
44
-
45
- $env->field( '_company_tagline', __( 'Company Tagline', 'wp-job-manager' ) )
46
- ->with_kind( WP_Job_Manager_REST_Field_Declaration::META )
47
- ->with_type( $env->type( 'string' ) ),
48
-
49
- $env->field( '_company_twitter', __( 'Company Twitter', 'wp-job-manager' ) )
50
- ->with_kind( WP_Job_Manager_REST_Field_Declaration::META )
51
- ->with_type( $env->type( 'string' ) ),
52
-
53
- $env->field( '_company_video', __( 'Company Video', 'wp-job-manager' ) )
54
- ->with_kind( WP_Job_Manager_REST_Field_Declaration::META )
55
- ->with_type( $env->type( 'string' ) ),
56
-
57
- $env->field( '_filled', __( 'Position Filled', 'wp-job-manager' ) )
58
- ->with_kind( WP_Job_Manager_REST_Field_Declaration::META )
59
- ->with_type( $env->type( 'boolean' ) ),
60
- );
61
-
62
- // These caps are more related to updating.
63
- if ( $current_user->has_cap( 'manage_job_listings' ) ) {
64
- $declarations[] = $env->field( '_featured', __( 'Featured Listing', 'wp-job-manager' ) )
65
- ->with_kind( WP_Job_Manager_REST_Field_Declaration::META )
66
- ->with_type( $env->type( 'boolean' ) );
67
-
68
- $declarations[] = $env->field( '_job_expires', __( 'Listing Expiry Date', 'wp-job-manager' ) )
69
- ->with_type( $env->type( 'string' ) )
70
- ->with_kind( WP_Job_Manager_REST_Field_Declaration::META )
71
- ->with_before_get( 'format_job_expires' );
72
- }
73
-
74
- if ( $current_user->has_cap( 'edit_others_job_listings' ) ) {
75
- $declarations[] = $env->field( '_job_author', __( 'Posted by', 'wp-job-manager' ) )
76
- ->with_type( $env->type( 'string' ) )
77
- ->with_kind( WP_Job_Manager_REST_Field_Declaration::META );
78
- }
79
-
80
- /**
81
- * Exposed so extension developers can add their custom fields.
82
- *
83
- * If the fields map to meta, you can follow the
84
- * Example of the above declarations to shape s your own (remember to add any validation or formatting callbacks).
85
- * For more constom things, you might want to check out Derived fields.
86
- *
87
- * @param array $declarations The Declarations so far.
88
- * @param WP_Job_Manager_REST_Environment $env Environment.
89
- *
90
- * @return array
91
- */
92
- return (array) apply_filters( 'wpjm_rest_api_job_listings_declare_fields', $declarations, $env );
93
- }
94
-
95
- /**
96
- * Format the date of Job_Expires
97
- *
98
- * @param mixed $value Value.
99
- * @return bool|string
100
- */
101
- public function format_job_expires( $value ) {
102
- return ! empty( $value ) ? date( 'Y-m-d H:i:s', strtotime( $value ) ) : '';
103
- }
104
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/rest-api/class-wp-job-manager-models-job-types-custom-fields.php DELETED
@@ -1,64 +0,0 @@
1
- <?php
2
- /**
3
- * Declaration of Job Types 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_Types_Custom_Fields
14
- */
15
- class WP_Job_Manager_Models_Job_Types_Custom_Fields extends WP_Job_Manager_REST_Model
16
- implements WP_Job_Manager_REST_Interfaces_Model {
17
-
18
- /**
19
- * Accepted employment types
20
- *
21
- * @var array
22
- */
23
- private static $accepted_employment_types = array();
24
-
25
- /**
26
- * Declare Fields
27
- *
28
- * @return array
29
- * @throws WP_Job_Manager_REST_Exception Thrown during error while processing of request.
30
- */
31
- public function declare_fields() {
32
- $env = $this->get_environment();
33
- $employment_types = wpjm_job_listing_employment_type_options();
34
- self::$accepted_employment_types = array_keys( $employment_types );
35
- return array(
36
- $env->field( 'employment_type', esc_html__( 'Employment Type', 'wp-job-manager' ) )
37
- ->with_kind( WP_Job_Manager_REST_Field_Declaration::META )
38
- ->with_type( $env->type( 'string' ) )
39
- ->with_choices( self::$accepted_employment_types ),
40
- );
41
- }
42
-
43
- /**
44
- * Validate this
45
- *
46
- * @return bool|WP_Error
47
- * @throws WP_Job_Manager_REST_Exception Thrown during error while processing of request.
48
- */
49
- public function validate() {
50
- $employment_type = $this->get( 'employment_type' );
51
- if ( ! empty( $employment_type ) && ! in_array( $employment_type, self::$accepted_employment_types, true ) ) {
52
- return new WP_Error(
53
- 'invalid_employment_type',
54
- esc_html__( 'Invalid Employment Type', 'wp-job-manager' ),
55
- array(
56
- 'input' => $employment_type,
57
- 'acceptable_values' => self::$accepted_employment_types,
58
- 'status' => 400,
59
- )
60
- );
61
- }
62
- return parent::validate();
63
- }
64
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/rest-api/class-wp-job-manager-models-settings.php DELETED
@@ -1,109 +0,0 @@
1
- <?php
2
- /**
3
- * Declaration of our Settings Model
4
- *
5
- * @package WPJM/REST
6
- */
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit;
10
- }
11
-
12
- /**
13
- * Class WP_Job_Manager_Models_Settings
14
- */
15
- class WP_Job_Manager_Models_Settings extends WP_Job_Manager_REST_Model_Settings
16
- implements WP_Job_Manager_REST_Interfaces_Permissions_Provider {
17
-
18
-
19
- /**
20
- * Fields that have to be valid page ids.
21
- *
22
- * @var array|null
23
- */
24
- private static $fields_requiring_page_id_validation = null;
25
-
26
- /**
27
- * Get Job Manager Settings
28
- *
29
- * @return array
30
- */
31
- public function get_settings() {
32
- if ( ! class_exists( 'WP_Job_Manager_Settings' ) ) {
33
- $parent = dirname( dirname( __FILE__ ) );
34
- if ( ! function_exists( 'get_editable_roles' ) ) {
35
- // WP_Job_Manager_Settings needs this for user roles.
36
- include_once ABSPATH . 'wp-admin/includes/user.php';
37
- }
38
- include_once $parent . '/admin/class-wp-job-manager-settings.php';
39
- }
40
-
41
- return WP_Job_Manager_Settings::instance()->get_settings();
42
- }
43
-
44
- /**
45
- * Adds validations to fields requiring page ids.
46
- *
47
- * @param string $field_name The fields name.
48
- * @param WP_Job_Manager_REST_Field_Declaration_Builder $field_builder The field builder.
49
- * @param array $field_data The field data.
50
- * @param WP_Job_Manager_REST_Environment $env The definition.
51
- *
52
- * @throws WP_Job_Manager_REST_Exception Thrown during error while processing of request.
53
- */
54
- protected function on_field_setup( $field_name, $field_builder, $field_data, $env ) {
55
- if ( in_array( $field_name, self::get_fields_requiring_page_id_validation(), true ) ) {
56
- $field_builder->with_type( $env->type( 'integer' ) )
57
- ->with_validations( 'validate_page_id_belongs_to_valid_page' );
58
- }
59
- }
60
-
61
- /**
62
- * Validates that a page_id points to a valid page.
63
- *
64
- * @param int $id The id.
65
- * @return bool|WP_Error
66
- */
67
- public function validate_page_id_belongs_to_valid_page( $id ) {
68
- $content = get_post( $id );
69
-
70
- if ( ! empty( $content ) && 'page' === $content->post_type ) {
71
- return true;
72
- }
73
-
74
- return new WP_Error( 'invalid-page-id', __( 'Invalid page ID provided', 'wp-job-manager' ) );
75
- }
76
-
77
- /**
78
- * Permissions for accessing Settings.
79
- *
80
- * @param WP_REST_Request $request The request.
81
- * @param string $action The action.
82
- *
83
- * @return bool
84
- */
85
- public function permissions_check( $request, $action ) {
86
- return current_user_can( 'manage_options' );
87
- }
88
-
89
- /**
90
- * Lazy-load field names requiring Page ID Validation.
91
- *
92
- * @return array
93
- */
94
- private function get_fields_requiring_page_id_validation() {
95
- if ( null === self::$fields_requiring_page_id_validation ) {
96
- self::$fields_requiring_page_id_validation = (array) apply_filters(
97
- 'wpjm_rest_api_settings_fields_requiring_page_id_validation',
98
- array(
99
- 'job_manager_submit_job_form_page_id',
100
- 'job_manager_job_dashboard_page_id',
101
- 'job_manager_jobs_page_id',
102
- )
103
- );
104
- }
105
-
106
- return self::$fields_requiring_page_id_validation;
107
- }
108
- }
109
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/rest-api/class-wp-job-manager-models-status.php DELETED
@@ -1,50 +0,0 @@
1
- <?php
2
- /**
3
- * Declaration of our Status Model
4
- *
5
- * @package WPJM/REST
6
- */
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit;
10
- }
11
-
12
- /**
13
- * Class WP_Job_Manager_Models_Status
14
- */
15
- class WP_Job_Manager_Models_Status extends WP_Job_Manager_REST_Model
16
- implements WP_Job_Manager_REST_Interfaces_Permissions_Provider {
17
-
18
-
19
- /**
20
- * Declare our fields
21
- *
22
- * @return array
23
- * @throws WP_Job_Manager_REST_Exception Exc.
24
- */
25
- public function declare_fields() {
26
- $env = $this->get_environment();
27
- return array(
28
- $env->field( 'run_page_setup', 'Should we run page setup' )
29
- ->with_type( $env->type( 'boolean' ) ),
30
- );
31
- }
32
-
33
- /**
34
- * Handle Permissions for a REST Controller Action
35
- *
36
- * @param WP_REST_Request $request The request.
37
- * @param string $action The action (e.g. index, create update etc).
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
- }
49
- }
50
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/rest-api/class-wp-job-manager-registrable-job-categories.php DELETED
@@ -1,42 +0,0 @@
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 DELETED
@@ -1,238 +0,0 @@
1
- <?php
2
- /**
3
- * Used for RESTifying the job_listing post type
4
- *
5
- * Adds custom fields. Needs a model definition that will provide the extra fields.
6
- *
7
- * @package WPJM/REST
8
- */
9
-
10
- if ( ! defined( 'ABSPATH' ) ) {
11
- exit;
12
- }
13
-
14
- /**
15
- * Class MT_Controller_Extension
16
- */
17
- class WP_Job_Manager_Registrable_Job_Listings implements WP_Job_Manager_REST_Interfaces_Registrable {
18
-
19
- /**
20
- * Environment
21
- *
22
- * @var WP_Job_Manager_REST_Environment
23
- */
24
- private $environment;
25
-
26
- /**
27
- * Object to extend
28
- *
29
- * @var string
30
- */
31
- private $object_to_extend;
32
-
33
- /**
34
- * Model def.
35
- *
36
- * @var WP_Job_Manager_REST_Model_Factory
37
- */
38
- private $model_factory;
39
-
40
- /**
41
- * Model Definition name, This should be a valid Model definition at registration time, otherwise register throws
42
- *
43
- * @var string
44
- */
45
- private $model_class;
46
-
47
- /**
48
- * REST Field name, the field name we will nest under
49
- *
50
- * @var string
51
- */
52
- private $rest_field_name;
53
-
54
- /**
55
- * Constructor.
56
- *
57
- * @param string $object_to_extend Post type.
58
- * @param string $model_class Model Class name.
59
- * @param string $rest_field_name The REST field name.
60
- */
61
- public function __construct( $object_to_extend, $model_class, $rest_field_name ) {
62
- $this->model_class = $model_class;
63
- $this->object_to_extend = $object_to_extend;
64
- $this->rest_field_name = $rest_field_name;
65
- }
66
-
67
- /**
68
- * Register This Controller
69
- *
70
- * @param WP_Job_Manager_REST_Environment $environment The Environment to use.
71
- * @throws WP_Job_Manager_REST_Exception Throws.
72
- *
73
- * @return bool|WP_Error true if valid otherwise error.
74
- */
75
- public function register( $environment ) {
76
- global $wp_post_types;
77
- $post_type_name = $this->object_to_extend;
78
- if ( ! isset( $wp_post_types[ $post_type_name ] ) ) {
79
- return false;
80
- }
81
-
82
- if ( $wp_post_types[ $post_type_name ]->show_in_rest ) {
83
- return true;
84
- }
85
-
86
- // Optionally customize the rest_base or controller class.
87
- $wp_post_types[ $post_type_name ]->show_in_rest = true;
88
- $wp_post_types[ $post_type_name ]->rest_base = 'job-listings';
89
- $wp_post_types[ $post_type_name ]->rest_controller_class = 'WP_REST_Posts_Controller';
90
-
91
- $this->environment = $environment;
92
- $this->model_factory = $this->environment->model( $this->model_class );
93
- if ( ! $this->model_factory ) {
94
- return new WP_Error( 'model-not-found' );
95
- }
96
- register_rest_field(
97
- $this->object_to_extend,
98
- $this->rest_field_name,
99
- array(
100
- 'get_callback' => array( $this, 'get_fields' ),
101
- 'update_callback' => array( $this, 'update_fields' ),
102
- 'schema' => $this->get_item_schema(),
103
- )
104
- );
105
-
106
- return true;
107
- }
108
-
109
- /**
110
- * Get Item Schema
111
- *
112
- * @return array
113
- */
114
- public function get_item_schema() {
115
- $fields = $this->model_factory->get_fields();
116
- $properties = array();
117
- $required = array();
118
- foreach ( $fields as $field_declaration ) {
119
- /**
120
- * Our declaration
121
- *
122
- * @var WP_Job_Manager_REST_Field_Declaration $field_declaration
123
- */
124
- $properties[ $field_declaration->get_data_transfer_name() ] = $field_declaration->as_item_schema_property();
125
- if ( $field_declaration->is_required() ) {
126
- $required[] = $field_declaration->get_data_transfer_name();
127
- }
128
- }
129
- $schema = array(
130
- '$schema' => 'http://json-schema.org/schema#',
131
- 'title' => $this->model_factory->get_name(),
132
- 'type' => 'object',
133
- 'properties' => (array) apply_filters( 'mixtape_rest_api_schema_properties', $properties, $this->model_factory ),
134
- );
135
-
136
- if ( ! empty( $required ) ) {
137
- $schema['required'] = $required;
138
- }
139
-
140
- return $schema;
141
- }
142
-
143
- /**
144
- * Our Get Fields.
145
- *
146
- * @param array $object Object.
147
- * @param string $field_name Field Name.
148
- * @param WP_REST_Request $request Request.
149
- * @param string $object_type Object Type.
150
- *
151
- * @return mixed|string
152
- * @throws WP_Job_Manager_REST_Exception If type not there.
153
- */
154
- public function get_fields( $object, $field_name, $request, $object_type ) {
155
- if ( 'job_listing' !== $object_type ) {
156
- return null;
157
- }
158
-
159
- if ( $this->rest_field_name !== $field_name ) {
160
- return null;
161
- }
162
-
163
- $object_id = absint( $object['id'] );
164
- $model = $this->get_model( $object_id );
165
- return $model->to_dto();
166
- }
167
-
168
- /**
169
- * Get a model if exists
170
- *
171
- * @param int $object_id Object ID.
172
- * @return WP_Job_Manager_REST_Interfaces_Model
173
- */
174
- private function get_model( $object_id ) {
175
- $data = array();
176
- foreach ( $this->model_factory->get_fields() as $field_declaration ) {
177
- $field_name = $field_declaration->get_name();
178
- if ( metadata_exists( 'post', $object_id, $field_name ) ) {
179
- $meta = get_post_meta( $object_id, $field_name, true );
180
- $data[ $field_name ] = $meta;
181
- }
182
- }
183
-
184
- return $this->model_factory->create(
185
- $data,
186
- array(
187
- 'deserialize' => true,
188
- )
189
- );
190
- }
191
-
192
- /**
193
- * Our Reader.
194
- *
195
- * @param mixed $data Data.
196
- * @param array|object $object Object.
197
- * @param string $field_name Field Name.
198
- * @param WP_REST_Request $request Request.
199
- * @param string $object_type Object Type.
200
- *
201
- * @return mixed|string
202
- * @throws WP_Job_Manager_REST_Exception If type not there.
203
- */
204
- public function update_fields( $data, $object, $field_name, $request, $object_type ) {
205
- if ( 'job_listing' !== $object_type ) {
206
- return null;
207
- }
208
-
209
- if ( $this->rest_field_name !== $field_name ) {
210
- return null;
211
- }
212
-
213
- $object_id = absint( $object->ID );
214
- $existing_model = $this->get_model( $object_id );
215
-
216
- $updated = $existing_model->update_from_array( $data );
217
- if ( is_wp_error( $updated ) ) {
218
- return $updated;
219
- }
220
-
221
- $maybe_validation_error = $updated->validate();
222
- if ( is_wp_error( $maybe_validation_error ) ) {
223
- return $maybe_validation_error;
224
- }
225
-
226
- $serialized_data = $updated->serialize( WP_Job_Manager_REST_Field_Declaration::META );
227
-
228
- foreach ( $serialized_data as $field_name => $val ) {
229
- if ( metadata_exists( 'post', $object_id, $field_name ) ) {
230
- update_post_meta( $object_id, $field_name, $val );
231
- } else {
232
- add_post_meta( $object_id, $field_name, $val );
233
- }
234
- }
235
-
236
- return true;
237
- }
238
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/rest-api/class-wp-job-manager-registrable-job-types.php DELETED
@@ -1,42 +0,0 @@
1
- <?php
2
- /**
3
- * Exposes Job Types 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_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 DELETED
@@ -1,239 +0,0 @@
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(
83
- $taxonomy_type,
84
- $this->rest_field_name,
85
- array(
86
- 'get_callback' => array( $this, 'get_taxonomy_term' ),
87
- 'update_callback' => array( $this, 'update_taxonomy_term' ),
88
- 'schema' => $this->get_item_schema(),
89
- )
90
- );
91
-
92
- return true;
93
- }
94
-
95
- /**
96
- * Get Item Schema
97
- *
98
- * @return array
99
- */
100
- public function get_item_schema() {
101
- $fields = $this->model_prototype->get_fields();
102
- $properties = array();
103
- $required = array();
104
- foreach ( $fields as $field_declaration ) {
105
- /**
106
- * Our declaration
107
- *
108
- * @var WP_Job_Manager_REST_Field_Declaration $field_declaration
109
- */
110
- $properties[ $field_declaration->get_data_transfer_name() ] = $field_declaration->as_item_schema_property();
111
- if ( $field_declaration->is_required() ) {
112
- $required[] = $field_declaration->get_data_transfer_name();
113
- }
114
- }
115
- $schema = array(
116
- '$schema' => 'http://json-schema.org/schema#',
117
- 'title' => $this->model_prototype->get_name(),
118
- 'type' => 'object',
119
- 'properties' => (array) apply_filters( 'mixtape_rest_api_schema_properties', $properties, $this->model_prototype ),
120
- );
121
-
122
- if ( ! empty( $required ) ) {
123
- $schema['required'] = $required;
124
- }
125
-
126
- return $schema;
127
- }
128
-
129
- /**
130
- * Our Get Fields.
131
- *
132
- * @param array $object Object.
133
- * @param string $field_name Field Name.
134
- * @param WP_REST_Request $request Request.
135
- * @param string $object_type Object Type.
136
- *
137
- * @return mixed|string
138
- * @throws WP_Job_Manager_REST_Exception If type not there.
139
- */
140
- public function get_taxonomy_term( $object, $field_name, $request, $object_type ) {
141
- if ( $this->get_taxonomy_type() !== $object_type ) {
142
- return null;
143
- }
144
-
145
- if ( $this->rest_field_name !== $field_name ) {
146
- return null;
147
- }
148
-
149
- $object_id = absint( $object['id'] );
150
- $model = $this->get_model( $object_id );
151
- return $model->to_dto();
152
- }
153
-
154
- /**
155
- * Get a model if exists
156
- *
157
- * @param int $object_id Object ID.
158
- * @return WP_Job_Manager_REST_Interfaces_Model
159
- * @throws WP_Job_Manager_REST_Exception On Error.
160
- */
161
- private function get_model( $object_id ) {
162
- $data = array();
163
- foreach ( $this->model_prototype->get_fields( WP_Job_Manager_REST_Field_Declaration::META ) as $field_declaration ) {
164
- $field_name = $field_declaration->get_name();
165
- if ( metadata_exists( 'term', $object_id, $field_name ) ) {
166
- $meta = get_term_meta( $object_id, $field_name, true );
167
- $data[ $field_name ] = $meta;
168
- }
169
- }
170
-
171
- return $this->model_prototype->create(
172
- $data,
173
- array(
174
- 'deserialize' => true,
175
- )
176
- );
177
- }
178
-
179
- /**
180
- * Our Reader.
181
- *
182
- * @param mixed $data Data.
183
- * @param object $object Object.
184
- * @param string $field_name Field Name.
185
- * @param WP_REST_Request $request Request.
186
- * @param string $object_type Object Type.
187
- *
188
- * @return mixed|string
189
- * @throws WP_Job_Manager_REST_Exception If type not there.
190
- */
191
- public function update_taxonomy_term( $data, $object, $field_name, $request, $object_type ) {
192
- if ( $this->get_taxonomy_type() !== $object_type ) {
193
- return null;
194
- }
195
-
196
- if ( $this->rest_field_name !== $field_name ) {
197
- return null;
198
- }
199
-
200
- if ( ! is_a( $object, 'WP_Term' ) ) {
201
- return null;
202
- }
203
-
204
- $rest_base = $this->get_rest_base();
205
- $term_id = absint( $object->term_id );
206
- if ( ! $term_id ) {
207
- // No way to update this. Bail.
208
- return new WP_Error(
209
- $rest_base . '-error-invalid-id', $rest_base . '-error-invalid-id',
210
- array(
211
- 'status' => 400,
212
- )
213
- );
214
- }
215
- $existing_model = $this->get_model( $term_id );
216
-
217
- $updated = $existing_model->update_from_array( $data );
218
- if ( is_wp_error( $updated ) ) {
219
- return $updated;
220
- }
221
-
222
- $maybe_validation_error = $updated->sanitize()->validate();
223
- if ( is_wp_error( $maybe_validation_error ) ) {
224
- return $maybe_validation_error;
225
- }
226
-
227
- $serialized_data = $updated->serialize( WP_Job_Manager_REST_Field_Declaration::META );
228
-
229
- foreach ( $serialized_data as $field_name => $val ) {
230
- if ( metadata_exists( 'term', $term_id, $field_name ) ) {
231
- update_term_meta( $term_id, $field_name, $val );
232
- } else {
233
- add_term_meta( $term_id, $field_name, $val );
234
- }
235
- }
236
-
237
- return true;
238
- }
239
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/rest-api/class-wp-job-manager-rest-api.php DELETED
@@ -1,128 +0,0 @@
1
- <?php
2
- /**
3
- * The REST API Initializer
4
- *
5
- * @package WPJM/REST
6
- */
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit;
10
- }
11
-
12
- /**
13
- * Class WP_Job_Manager_REST_API
14
- */
15
- class WP_Job_Manager_REST_API {
16
-
17
- /**
18
- * Is the api enabled?
19
- *
20
- * @var bool
21
- */
22
- private $is_rest_api_enabled;
23
- /**
24
- * Our bootstrap
25
- *
26
- * @var WP_Job_Manager_REST_Bootstrap
27
- */
28
- private $wpjm_rest_api;
29
- /**
30
- * The plugin base dir
31
- *
32
- * @var string
33
- */
34
- private $base_dir;
35
-
36
- /**
37
- * WP_Job_Manager_REST_API constructor.
38
- *
39
- * @param string $base_dir The base dir.
40
- */
41
- public function __construct( $base_dir ) {
42
- $this->base_dir = trailingslashit( $base_dir );
43
- $this->is_rest_api_enabled = defined( 'WPJM_REST_API_ENABLED' ) && ( true === constant( 'WPJM_REST_API_ENABLED' ) );
44
- $file = $this->base_dir . 'lib/wpjm_rest/class-wp-job-manager-rest-bootstrap.php';
45
- if ( file_exists( $file ) && $this->is_rest_api_enabled ) {
46
- include_once $file;
47
- $this->wpjm_rest_api = WP_Job_Manager_REST_Bootstrap::create();
48
- $this->wpjm_rest_api
49
- ->environment()
50
- ->get_event_dispatcher()
51
- ->add_action( 'environment_before_start', array( $this, 'define_api' ) );
52
- $this->wpjm_rest_api->run();
53
- }
54
- }
55
-
56
- /**
57
- * Get WP_Job_Manager_REST_Bootstrap
58
- *
59
- * @return WP_Job_Manager_REST_Bootstrap
60
- */
61
- public function get_bootstrap() {
62
- return $this->wpjm_rest_api;
63
- }
64
-
65
- /**
66
- * Initialize the REST API
67
- *
68
- * @return WP_Job_Manager_REST_API $this
69
- */
70
- public function init() {
71
- if ( ! $this->is_rest_api_enabled ) {
72
- return $this;
73
- }
74
- $this->define_api( $this->wpjm_rest_api->environment() );
75
- return $this;
76
- }
77
-
78
- /**
79
- * Define our REST API Models and Controllers
80
- *
81
- * @param WP_Job_Manager_REST_Environment $env The Environment.
82
- *
83
- * @throws WP_Job_Manager_REST_Exception Thrown during error while processing of request.
84
- */
85
- public function define_api( $env ) {
86
- if ( ! is_a( $env, 'WP_Job_Manager_REST_Environment' ) ) {
87
- return;
88
- }
89
-
90
- include_once 'class-wp-job-manager-models-settings.php';
91
- include_once 'class-wp-job-manager-models-status.php';
92
- include_once 'class-wp-job-manager-filters-status.php';
93
- include_once 'class-wp-job-manager-data-stores-status.php';
94
- include_once 'class-wp-job-manager-controllers-status.php';
95
- include_once 'class-wp-job-manager-models-job-listings-custom-fields.php';
96
- include_once 'class-wp-job-manager-models-job-types-custom-fields.php';
97
- include_once 'class-wp-job-manager-models-job-categories-custom-fields.php';
98
- include_once 'class-wp-job-manager-registrable-job-listings.php';
99
- include_once 'class-wp-job-manager-registrable-taxonomy-type.php';
100
- include_once 'class-wp-job-manager-registrable-job-types.php';
101
- include_once 'class-wp-job-manager-registrable-job-categories.php';
102
-
103
- // Models.
104
- $env->define_model( 'WP_Job_Manager_Models_Settings' )
105
- ->with_data_store( new WP_Job_Manager_REST_Data_Store_Option( $env->model( 'WP_Job_Manager_Models_Settings' ) ) );
106
- $env->define_model( 'WP_Job_Manager_Models_Status' )
107
- ->with_data_store( new WP_Job_Manager_Data_Stores_Status( $env->model( 'WP_Job_Manager_Models_Status' ) ) );
108
- $env->define_model( 'WP_Job_Manager_Filters_Status' );
109
- $env->define_model( 'WP_Job_Manager_Models_Job_Listings_Custom_Fields' );
110
- $env->define_model( 'WP_Job_Manager_Models_Job_Types_Custom_Fields' );
111
- $env->define_model( 'WP_Job_Manager_Models_Job_Categories_Custom_Fields' );
112
-
113
- // Endpoints.
114
- $env->rest_api( 'wpjm/v1' )
115
- ->add_endpoint( new WP_Job_Manager_REST_Controller_Settings( '/settings', 'WP_Job_Manager_Models_Settings' ) )
116
- ->add_endpoint( new WP_Job_Manager_Controllers_Status( '/status', 'WP_Job_Manager_Models_Status' ) );
117
- $env->add_registrable(
118
- new WP_Job_Manager_Registrable_Job_Listings(
119
- 'job_listing',
120
- 'WP_Job_Manager_Models_Job_Listings_Custom_Fields',
121
- 'fields'
122
- )
123
- );
124
- $env->add_registrable( new WP_Job_Manager_Registrable_Job_Types() );
125
- $env->add_registrable( new WP_Job_Manager_Registrable_Job_Categories() );
126
- }
127
- }
128
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/widgets/class-wp-job-manager-widget-featured-jobs.php DELETED
@@ -1,138 +0,0 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) {
3
- exit;
4
- }
5
-
6
- /**
7
- * Featured Jobs widget.
8
- *
9
- * @package wp-job-manager
10
- * @since 1.21.0
11
- */
12
- class WP_Job_Manager_Widget_Featured_Jobs extends WP_Job_Manager_Widget {
13
-
14
- /**
15
- * Constructor.
16
- */
17
- public function __construct() {
18
- global $wp_post_types;
19
-
20
- // translators: Placeholder %s is the plural label for the job listing post type.
21
- $this->widget_name = sprintf( __( 'Featured %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name );
22
- $this->widget_cssclass = 'job_manager widget_featured_jobs';
23
- $this->widget_description = __( 'Display a list of featured listings on your site.', 'wp-job-manager' );
24
- $this->widget_id = 'widget_featured_jobs';
25
- $this->settings = array(
26
- 'title' => array(
27
- 'type' => 'text',
28
- // translators: Placeholder %s is the plural label for the job listing post type.
29
- 'std' => sprintf( __( 'Featured %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name ),
30
- 'label' => __( 'Title', 'wp-job-manager' ),
31
- ),
32
- 'number' => array(
33
- 'type' => 'number',
34
- 'step' => 1,
35
- 'min' => 1,
36
- 'max' => '',
37
- 'std' => 10,
38
- 'label' => __( 'Number of listings to show', 'wp-job-manager' ),
39
- ),
40
- 'orderby' => array(
41
- 'type' => 'select',
42
- 'std' => 'date',
43
- 'label' => __( 'Sort By', 'wp-job-manager' ),
44
- 'options' => array(
45
- 'date' => __( 'Date', 'wp-job-manager' ),
46
- 'title' => __( 'Title', 'wp-job-manager' ),
47
- 'author' => __( 'Author', 'wp-job-manager' ),
48
- 'rand_featured' => __( 'Random', 'wp-job-manager' ),
49
- ),
50
- ),
51
- 'order' => array(
52
- 'type' => 'select',
53
- 'std' => 'DESC',
54
- 'label' => __( 'Sort Direction', 'wp-job-manager' ),
55
- 'options' => array(
56
- 'ASC' => __( 'Ascending', 'wp-job-manager' ),
57
- 'DESC' => __( 'Descending', 'wp-job-manager' ),
58
- ),
59
- ),
60
- );
61
- parent::__construct();
62
- }
63
-
64
- /**
65
- * Echoes the widget content.
66
- *
67
- * @see WP_Widget
68
- * @param array $args
69
- * @param array $instance
70
- */
71
- public function widget( $args, $instance ) {
72
- wp_enqueue_style( 'wp-job-manager-job-listings' );
73
-
74
- if ( $this->get_cached_widget( $args ) ) {
75
- return;
76
- }
77
-
78
- $instance = array_merge( $this->get_default_instance(), $instance );
79
-
80
- ob_start();
81
-
82
- $title_instance = esc_attr( $instance['title'] );
83
- $number = absint( $instance['number'] );
84
- $orderby = esc_attr( $instance['orderby'] );
85
- $order = esc_attr( $instance['order'] );
86
- $title = apply_filters( 'widget_title', $title_instance, $instance, $this->id_base );
87
- $jobs = get_job_listings(
88
- array(
89
- 'posts_per_page' => $number,
90
- 'orderby' => $orderby,
91
- 'order' => $order,
92
- 'featured' => true,
93
- )
94
- );
95
-
96
- if ( $jobs->have_posts() ) : ?>
97
-
98
- <?php echo $args['before_widget']; // WPCS: XSS ok. ?>
99
-
100
- <?php
101
- if ( $title ) {
102
- echo $args['before_title'] . esc_html( $title ) . $args['after_title']; // WPCS: XSS ok.
103
- }
104
- ?>
105
-
106
- <ul class="job_listings">
107
-
108
- <?php
109
- while ( $jobs->have_posts() ) :
110
- $jobs->the_post();
111
- ?>
112
-
113
- <?php get_job_manager_template_part( 'content-widget', 'job_listing' ); ?>
114
-
115
- <?php endwhile; ?>
116
-
117
- </ul>
118
-
119
- <?php echo $args['after_widget']; // WPCS: XSS ok. ?>
120
-
121
- <?php else : ?>
122
-
123
- <?php get_job_manager_template_part( 'content-widget', 'no-jobs-found' ); ?>
124
-
125
- <?php
126
- endif;
127
-
128
- wp_reset_postdata();
129
-
130
- $content = ob_get_clean();
131
-
132
- echo $content; // WPCS: XSS ok.
133
-
134
- $this->cache_widget( $args, $content );
135
- }
136
- }
137
-
138
- register_widget( 'WP_Job_Manager_Widget_Featured_Jobs' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/widgets/class-wp-job-manager-widget-recent-jobs.php DELETED
@@ -1,155 +0,0 @@
1
- <?php
2
- if ( ! defined( 'ABSPATH' ) ) {
3
- exit;
4
- }
5
-
6
- /**
7
- * Recent Jobs widget.
8
- *
9
- * @package wp-job-manager
10
- * @since 1.0.0
11
- */
12
- class WP_Job_Manager_Widget_Recent_Jobs extends WP_Job_Manager_Widget {
13
-
14
- /**
15
- * Constructor.
16
- */
17
- public function __construct() {
18
- global $wp_post_types;
19
-
20
- // translators: Placeholder %s is the plural label for the job listing post type.
21
- $this->widget_name = sprintf( __( 'Recent %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name );
22
- $this->widget_cssclass = 'job_manager widget_recent_jobs';
23
- $this->widget_description = __( 'Display a list of recent listings on your site, optionally matching a keyword and location.', 'wp-job-manager' );
24
- $this->widget_id = 'widget_recent_jobs';
25
- $this->settings = array(
26
- 'title' => array(
27
- 'type' => 'text',
28
- // translators: Placeholder %s is the plural label for the job listing post type.
29
- 'std' => sprintf( __( 'Recent %s', 'wp-job-manager' ), $wp_post_types['job_listing']->labels->name ),
30
- 'label' => __( 'Title', 'wp-job-manager' ),
31
- ),
32
- 'keyword' => array(
33
- 'type' => 'text',
34
- 'std' => '',
35
- 'label' => __( 'Keyword', 'wp-job-manager' ),
36
- ),
37
- 'location' => array(
38
- 'type' => 'text',
39
- 'std' => '',
40
- 'label' => __( 'Location', 'wp-job-manager' ),
41
- ),
42
- 'number' => array(
43
- 'type' => 'number',
44
- 'step' => 1,
45
- 'min' => 1,
46
- 'max' => '',
47
- 'std' => 10,
48
- 'label' => __( 'Number of listings to show', 'wp-job-manager' ),
49
- ),
50
- 'show_logo' => array(
51
- 'type' => 'checkbox',
52
- 'std' => 0,
53
- 'label' => esc_html__( 'Show Company Logo', 'wp-job-manager' ),
54
- ),
55
- );
56
-
57
- parent::__construct();
58
- }
59
-
60
- /**
61
- * Echoes the widget content.
62
- *
63
- * @see WP_Widget
64
- * @param array $args
65
- * @param array $instance
66
- */
67
- public function widget( $args, $instance ) {
68
- wp_enqueue_style( 'wp-job-manager-job-listings' );
69
-
70
- if ( $this->get_cached_widget( $args ) ) {
71
- return;
72
- }
73
-
74
- $instance = array_merge( $this->get_default_instance(), $instance );
75
-
76
- ob_start();
77
-
78
- $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base );
79
- $number = absint( $instance['number'] );
80
- $jobs = get_job_listings(
81
- array(
82
- 'search_location' => $instance['location'],
83
- 'search_keywords' => $instance['keyword'],
84
- 'posts_per_page' => $number,
85
- 'orderby' => 'date',
86
- 'order' => 'DESC',
87
- )
88
- );
89
- $show_logo = absint( $instance['show_logo'] );
90
-
91
- /**
92
- * Runs before Recent Jobs widget content.
93
- *
94
- * @since 1.29.1
95
- *
96
- * @param array $args
97
- * @param array $instance
98
- * @param WP_Query $jobs
99
- */
100
- do_action( 'job_manager_recent_jobs_widget_before', $args, $instance, $jobs );
101
-
102
- if ( $jobs->have_posts() ) : ?>
103
-
104
- <?php echo $args['before_widget']; // WPCS: XSS ok. ?>
105
-
106
- <?php
107
- if ( $title ) {
108
- echo $args['before_title'] . esc_html( $title ) . $args['after_title']; // WPCS: XSS ok.
109
- }
110
- ?>
111
-
112
- <ul class="job_listings">
113
-
114
- <?php
115
- while ( $jobs->have_posts() ) :
116
- $jobs->the_post();
117
- ?>
118
-
119
- <?php get_job_manager_template( 'content-widget-job_listing.php', array( 'show_logo' => $instance['show_logo'] ) ); ?>
120
-
121
- <?php endwhile; ?>
122
-
123
- </ul>
124
-
125
- <?php echo $args['after_widget']; // WPCS: XSS ok. ?>
126
-
127
- <?php else : ?>
128
-
129
- <?php get_job_manager_template_part( 'content-widget', 'no-jobs-found' ); ?>
130
-
131
- <?php
132
- endif;
133
-
134
- /**
135
- * Runs after Recent Jobs widget content.
136
- *
137
- * @since 1.29.1
138
- *
139
- * @param array $args
140
- * @param array $instance
141
- * @param WP_Query $jobs
142
- */
143
- do_action( 'job_manager_recent_jobs_widget_after', $args, $instance, $jobs );
144
-
145
- wp_reset_postdata();
146
-
147
- $content = ob_get_clean();
148
-
149
- echo $content; // WPCS: XSS ok.
150
-
151
- $this->cache_widget( $args, $content );
152
- }
153
- }
154
-
155
- register_widget( 'WP_Job_Manager_Widget_Recent_Jobs' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
languages/wp-job-manager.pot CHANGED
@@ -1,2198 +1,1406 @@
1
- # Copyright (C) 2018 Automattic
2
- # This file is distributed under the GPL2+.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: WP Job Manager 1.31.2\n"
6
- "Report-Msgid-Bugs-To: https://github.com/Automattic/WP-Job-Manager/issues\n"
7
- "POT-Creation-Date: 2018-07-12 16:24:59+00:00\n"
8
  "MIME-Version: 1.0\n"
9
- "Content-Type: text/plain; charset=utf-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
- "PO-Revision-Date: 2018-MO-DA HO:MI+ZONE\n"
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
- "Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
14
- "X-Generator: grunt-wp-i18n1.0.2\n"
15
 
16
- #: includes/3rd-party/wpml.php:84
17
- msgid "Page Not Set"
18
- msgstr ""
19
-
20
- #: includes/3rd-party/wpml.php:98
21
- #. translators: Placeholder (%s) is the URL to edit the primary language in
22
- #. WPML.
23
- msgid "<a href=\"%s\">Switch to primary language</a> to edit this setting."
24
- msgstr ""
25
-
26
- #: includes/abstracts/abstract-wp-job-manager-form.php:319
27
- #: includes/abstracts/abstract-wp-job-manager-form.php:334
28
- #. translators: Placeholder is for the label of the reCAPTCHA field.
29
- #. translators: %s is the name of the form validation that failed.
30
- msgid "\"%s\" check failed. Please try again."
31
- msgstr ""
32
-
33
- #: includes/admin/class-wp-job-manager-addons.php:124
34
- #: includes/admin/class-wp-job-manager-admin.php:155
35
- #: includes/admin/views/html-admin-page-addons.php:2
36
  msgid "WP Job Manager Add-ons"
37
  msgstr ""
38
 
39
- #: includes/admin/class-wp-job-manager-addons.php:131
40
- #: includes/helper/views/html-licences.php:6
41
- msgid "Licenses"
42
  msgstr ""
43
 
44
- #: includes/admin/class-wp-job-manager-admin.php:89
45
- #. translators: %s is the URL for the page where users can go to update
46
- #. WordPress.
47
- msgid ""
48
- "<strong>WP Job Manager</strong> requires a more recent version of "
49
- "WordPress. <a href=\"%s\">Please update WordPresse</a> to avoid issues."
50
  msgstr ""
51
 
52
- #: includes/admin/class-wp-job-manager-admin.php:101
53
- #. translators: Placeholder (%s) is the URL where users can go to update
54
- #. WordPress.
55
- msgid "<a href=\"%s\" style=\"color: red\">WordPress Update Required</a>"
56
  msgstr ""
57
 
58
- #: includes/admin/class-wp-job-manager-admin.php:152
59
  msgid "Settings"
60
  msgstr ""
61
 
62
- #: includes/admin/class-wp-job-manager-admin.php:155
63
  msgid "Add-ons"
64
  msgstr ""
65
 
66
- #: includes/admin/class-wp-job-manager-cpt.php:77
67
- #. translators: Placeholder (%s) is the plural name of the job listings post
68
- #. type.
69
  msgid "Approve %s"
70
  msgstr ""
71
 
72
- #: includes/admin/class-wp-job-manager-cpt.php:79
73
- #. translators: Placeholder (%s) is the plural name of the job listings post
74
- #. type.
75
- msgid "%s approved"
76
- msgstr ""
77
-
78
- #: includes/admin/class-wp-job-manager-cpt.php:84
79
- #. translators: Placeholder (%s) is the plural name of the job listings post
80
- #. type.
81
  msgid "Expire %s"
82
  msgstr ""
83
 
84
- #: includes/admin/class-wp-job-manager-cpt.php:86
85
- #. translators: Placeholder (%s) is the plural name of the job listings post
86
- #. type.
87
- msgid "%s expired"
88
- msgstr ""
89
-
90
- #: includes/admin/class-wp-job-manager-cpt.php:91
91
- #. translators: Placeholder (%s) is the plural name of the job listings post
92
- #. type.
93
- msgid "Mark %s Filled"
94
- msgstr ""
95
-
96
- #: includes/admin/class-wp-job-manager-cpt.php:93
97
- #. translators: Placeholder (%s) is the plural name of the job listings post
98
- #. type.
99
- msgid "%s marked as filled"
100
- msgstr ""
101
-
102
- #: includes/admin/class-wp-job-manager-cpt.php:98
103
- #. translators: Placeholder (%s) is the plural name of the job listings post
104
- #. type.
105
- msgid "Mark %s Not Filled"
106
  msgstr ""
107
 
108
- #: includes/admin/class-wp-job-manager-cpt.php:100
109
- #. translators: Placeholder (%s) is the plural name of the job listings post
110
- #. type.
111
- msgid "%s marked as not filled"
112
  msgstr ""
113
 
114
- #: includes/admin/class-wp-job-manager-cpt.php:310
115
  msgid "Select category"
116
  msgstr ""
117
 
118
- #: includes/admin/class-wp-job-manager-cpt.php:335
119
- msgid "Select Filled"
120
- msgstr ""
121
-
122
- #: includes/admin/class-wp-job-manager-cpt.php:339
123
- msgid "Filled"
124
- msgstr ""
125
-
126
- #: includes/admin/class-wp-job-manager-cpt.php:343
127
- msgid "Not Filled"
128
- msgstr ""
129
-
130
- #: includes/admin/class-wp-job-manager-cpt.php:354
131
- msgid "Select Featured"
132
- msgstr ""
133
-
134
- #: includes/admin/class-wp-job-manager-cpt.php:358
135
- msgid "Featured"
136
- msgstr ""
137
-
138
- #: includes/admin/class-wp-job-manager-cpt.php:362
139
- msgid "Not Featured"
140
- msgstr ""
141
-
142
- #: includes/admin/class-wp-job-manager-cpt.php:407
143
- #: includes/admin/class-wp-job-manager-cpt.php:464
144
  msgid "Position"
145
  msgstr ""
146
 
147
- #: includes/admin/class-wp-job-manager-cpt.php:424
148
- #. translators: %1$s is the singular name of the job listing post type; %2$s is
149
- #. the URL to view the listing.
150
- msgid "%1$s updated. <a href=\"%2$s\">View</a>"
151
  msgstr ""
152
 
153
- #: includes/admin/class-wp-job-manager-cpt.php:425
154
  msgid "Custom field updated."
155
  msgstr ""
156
 
157
- #: includes/admin/class-wp-job-manager-cpt.php:426
158
  msgid "Custom field deleted."
159
  msgstr ""
160
 
161
- #: includes/admin/class-wp-job-manager-cpt.php:428
162
- #. translators: %s is the singular name of the job listing post type.
163
  msgid "%s updated."
164
  msgstr ""
165
 
166
- #: includes/admin/class-wp-job-manager-cpt.php:430
167
- #. translators: %1$s is the singular name of the job listing post type; %2$s is
168
- #. the revision number.
169
- msgid "%1$s restored to revision from %2$s"
170
  msgstr ""
171
 
172
- #: includes/admin/class-wp-job-manager-cpt.php:432
173
- #. translators: %1$s is the singular name of the job listing post type; %2$s is
174
- #. the URL to view the listing.
175
- msgid "%1$s published. <a href=\"%2$s\">View</a>"
176
  msgstr ""
177
 
178
- #: includes/admin/class-wp-job-manager-cpt.php:434
179
- #. translators: %1$s is the singular name of the job listing post type; %2$s is
180
- #. the URL to view the listing.
181
  msgid "%s saved."
182
  msgstr ""
183
 
184
- #: includes/admin/class-wp-job-manager-cpt.php:436
185
- #. translators: %1$s is the singular name of the job listing post type; %2$s is
186
- #. the URL to preview the listing.
187
- msgid "%1$s submitted. <a target=\"_blank\" href=\"%2$s\">Preview</a>"
188
  msgstr ""
189
 
190
- #: includes/admin/class-wp-job-manager-cpt.php:439
191
- #. translators: %1$s is the singular name of the post type; %2$s is the date
192
- #. the post will be published; %3$s is the URL to preview the listing.
193
  msgid ""
194
- "%1$s scheduled for: <strong>%2$s</strong>. <a target=\"_blank\" "
195
- "href=\"%3$s\">Preview</a>"
196
  msgstr ""
197
 
198
- #: includes/admin/class-wp-job-manager-cpt.php:445
199
- #. translators: %1$s is the singular name of the job listing post type; %2$s is
200
- #. the URL to view the listing.
201
- msgid "%1$s draft updated. <a target=\"_blank\" href=\"%2$s\">Preview</a>"
202
  msgstr ""
203
 
204
- #: includes/admin/class-wp-job-manager-cpt.php:465
 
 
 
 
205
  msgid "Type"
206
  msgstr ""
207
 
208
- #: includes/admin/class-wp-job-manager-cpt.php:466
209
- #: includes/admin/class-wp-job-manager-writepanels.php:57
210
- #: includes/class-wp-job-manager-email-notifications.php:234
211
- #: includes/forms/class-wp-job-manager-form-submit-job.php:183
212
- #: includes/widgets/class-wp-job-manager-widget-recent-jobs.php:40
213
- #: templates/job-filters.php:35 templates/job-filters.php:36
214
  msgid "Location"
215
  msgstr ""
216
 
217
- #: includes/admin/class-wp-job-manager-cpt.php:467
218
  msgid "Status"
219
  msgstr ""
220
 
221
- #: includes/admin/class-wp-job-manager-cpt.php:468
222
  msgid "Posted"
223
  msgstr ""
224
 
225
- #: includes/admin/class-wp-job-manager-cpt.php:469
226
  msgid "Expires"
227
  msgstr ""
228
 
229
- #: includes/admin/class-wp-job-manager-cpt.php:470
230
- #: includes/admin/class-wp-job-manager-settings.php:157
231
  msgid "Categories"
232
  msgstr ""
233
 
234
- #: includes/admin/class-wp-job-manager-cpt.php:471
235
  msgid "Featured?"
236
  msgstr ""
237
 
238
- #: includes/admin/class-wp-job-manager-cpt.php:472
239
- #: includes/class-wp-job-manager-shortcodes.php:247
240
  msgid "Filled?"
241
  msgstr ""
242
 
243
- #: includes/admin/class-wp-job-manager-cpt.php:473
244
  msgid "Actions"
245
  msgstr ""
246
 
247
- #: includes/admin/class-wp-job-manager-cpt.php:538
248
- #. translators: %d is the post ID for the job listing.
249
  msgid "ID: %d"
250
  msgstr ""
251
 
252
- #: includes/admin/class-wp-job-manager-cpt.php:582
253
- #. translators: %s placeholder is the username of the user.
 
 
 
 
254
  msgid "by a guest"
255
  msgstr ""
256
 
257
- #: includes/admin/class-wp-job-manager-cpt.php:582
258
  msgid "by %s"
259
  msgstr ""
260
 
261
- #: includes/admin/class-wp-job-manager-cpt.php:601
262
  msgid "Approve"
263
  msgstr ""
264
 
265
- #: includes/admin/class-wp-job-manager-cpt.php:609
266
- #: includes/admin/class-wp-job-manager-writepanels.php:282
267
- #: includes/admin/class-wp-job-manager-writepanels.php:287
268
- #: includes/admin/class-wp-job-manager-writepanels.php:292
269
  msgid "View"
270
  msgstr ""
271
 
272
- #: includes/admin/class-wp-job-manager-cpt.php:616
273
- #: includes/class-wp-job-manager-post-types.php:251
274
- #: templates/job-dashboard.php:52 templates/job-dashboard.php:70
275
  msgid "Edit"
276
  msgstr ""
277
 
278
- #: includes/admin/class-wp-job-manager-cpt.php:623
279
- #: templates/job-dashboard.php:75
280
  msgid "Delete"
281
  msgstr ""
282
 
283
- #: includes/admin/class-wp-job-manager-permalink-settings.php:60
284
- msgid "Job base"
285
- msgstr ""
286
-
287
- #: includes/admin/class-wp-job-manager-permalink-settings.php:67
288
- msgid "Job category base"
289
- msgstr ""
290
-
291
- #: includes/admin/class-wp-job-manager-permalink-settings.php:74
292
- msgid "Job type base"
293
- msgstr ""
294
-
295
- #: includes/admin/class-wp-job-manager-settings.php:84
296
- msgid "General"
297
- msgstr ""
298
-
299
- #: includes/admin/class-wp-job-manager-settings.php:89
300
- msgid "Date Format"
301
- msgstr ""
302
-
303
- #: includes/admin/class-wp-job-manager-settings.php:90
304
- msgid ""
305
- "Choose how you want the published date for jobs to be displayed on the "
306
- "front-end."
307
- msgstr ""
308
-
309
- #: includes/admin/class-wp-job-manager-settings.php:93
310
- msgid "Relative to the current date (e.g., 1 day, 1 week, 1 month ago)"
311
- msgstr ""
312
-
313
- #: includes/admin/class-wp-job-manager-settings.php:94
314
- msgid "Default date format as defined in Settings"
315
- msgstr ""
316
-
317
- #: includes/admin/class-wp-job-manager-settings.php:100
318
- msgid "Google Maps API Key"
319
- msgstr ""
320
-
321
- #: includes/admin/class-wp-job-manager-settings.php:102
322
- #. translators: Placeholder %s is URL to set up a Google Maps API key.
323
- msgid ""
324
- "Google requires an API key to retrieve location information for job "
325
- "listings. Acquire an API key from the <a href=\"%s\">Google Maps API "
326
- "developer site</a>."
327
- msgstr ""
328
-
329
- #: includes/admin/class-wp-job-manager-settings.php:108
330
- msgid "Delete Data On Uninstall"
331
- msgstr ""
332
-
333
- #: includes/admin/class-wp-job-manager-settings.php:109
334
- msgid ""
335
- "Delete WP Job Manager data when the plugin is deleted. Once removed, this "
336
- "data cannot be restored."
337
- msgstr ""
338
-
339
- #: includes/admin/class-wp-job-manager-settings.php:117
340
- #: includes/class-wp-job-manager-post-types.php:245
341
- #: includes/class-wp-job-manager-post-types.php:342
342
  msgid "Job Listings"
343
  msgstr ""
344
 
345
- #: includes/admin/class-wp-job-manager-settings.php:123
346
  msgid "Listings Per Page"
347
  msgstr ""
348
 
349
- #: includes/admin/class-wp-job-manager-settings.php:124
350
- msgid "Number of job listings to display per page."
351
  msgstr ""
352
 
353
- #: includes/admin/class-wp-job-manager-settings.php:130
354
  msgid "Filled Positions"
355
  msgstr ""
356
 
357
- #: includes/admin/class-wp-job-manager-settings.php:131
358
  msgid "Hide filled positions"
359
  msgstr ""
360
 
361
- #: includes/admin/class-wp-job-manager-settings.php:132
362
- msgid "Filled positions will not display in your archives."
363
- msgstr ""
364
-
365
- #: includes/admin/class-wp-job-manager-settings.php:139
366
- msgid "Hide Expired Listings"
367
- msgstr ""
368
-
369
- #: includes/admin/class-wp-job-manager-settings.php:140
370
- msgid "Hide expired listings in job archives/search"
371
- msgstr ""
372
-
373
- #: includes/admin/class-wp-job-manager-settings.php:141
374
- msgid "Expired job listings will not be searchable."
375
  msgstr ""
376
 
377
- #: includes/admin/class-wp-job-manager-settings.php:148
378
- msgid "Hide Expired Listings Content"
379
  msgstr ""
380
 
381
- #: includes/admin/class-wp-job-manager-settings.php:149
382
- msgid "Hide content in expired single job listings"
383
  msgstr ""
384
 
385
- #: includes/admin/class-wp-job-manager-settings.php:150
386
  msgid ""
387
- "Your site will display the titles of expired listings, but not the content "
388
- "of the listings. Otherwise, expired listings display their full content "
389
- "minus the application area."
390
  msgstr ""
391
 
392
- #: includes/admin/class-wp-job-manager-settings.php:158
393
- msgid "Enable listing categories"
394
  msgstr ""
395
 
396
- #: includes/admin/class-wp-job-manager-settings.php:159
397
  msgid ""
398
- "This lets users select from a list of categories when submitting a job. "
399
- "Note: an admin has to create categories before site users can select them."
400
  msgstr ""
401
 
402
- #: includes/admin/class-wp-job-manager-settings.php:166
403
  msgid "Multi-select Categories"
404
  msgstr ""
405
 
406
- #: includes/admin/class-wp-job-manager-settings.php:167
407
- msgid "Default to category multiselect"
408
  msgstr ""
409
 
410
- #: includes/admin/class-wp-job-manager-settings.php:168
 
411
  msgid ""
412
- "The category selection box will default to allowing multiple selections on "
413
- "the [jobs] shortcode. Without this, users will only be able to select a "
414
- "single category when submitting jobs."
415
  msgstr ""
416
 
417
- #: includes/admin/class-wp-job-manager-settings.php:175
418
  msgid "Category Filter Type"
419
  msgstr ""
420
 
421
- #: includes/admin/class-wp-job-manager-settings.php:176
422
- msgid ""
423
- "Determines the logic used to display jobs when selecting multiple "
424
- "categories."
425
- msgstr ""
426
-
427
- #: includes/admin/class-wp-job-manager-settings.php:179
428
  msgid "Jobs will be shown if within ANY selected category"
429
  msgstr ""
430
 
431
- #: includes/admin/class-wp-job-manager-settings.php:180
432
  msgid "Jobs will be shown if within ALL selected categories"
433
  msgstr ""
434
 
435
- #: includes/admin/class-wp-job-manager-settings.php:186
436
- msgid "Types"
437
- msgstr ""
438
-
439
- #: includes/admin/class-wp-job-manager-settings.php:187
440
- msgid "Enable listing types"
441
- msgstr ""
442
-
443
- #: includes/admin/class-wp-job-manager-settings.php:188
444
- msgid ""
445
- "This lets users select from a list of types when submitting a job. Note: an "
446
- "admin has to create types before site users can select them."
447
- msgstr ""
448
-
449
- #: includes/admin/class-wp-job-manager-settings.php:195
450
- msgid "Multi-select Listing Types"
451
- msgstr ""
452
-
453
- #: includes/admin/class-wp-job-manager-settings.php:196
454
- msgid "Allow multiple types for listings"
455
- msgstr ""
456
-
457
- #: includes/admin/class-wp-job-manager-settings.php:197
458
- msgid ""
459
- "This allows users to select more than one type when submitting a job. The "
460
- "metabox on the post editor and the selection box on the front-end job "
461
- "submission form will both reflect this."
462
- msgstr ""
463
-
464
- #: includes/admin/class-wp-job-manager-settings.php:204
465
  msgid "Job Submission"
466
  msgstr ""
467
 
468
- #: includes/admin/class-wp-job-manager-settings.php:209
469
  msgid "Account Required"
470
  msgstr ""
471
 
472
- #: includes/admin/class-wp-job-manager-settings.php:210
473
- msgid "Require an account to submit listings"
474
  msgstr ""
475
 
476
- #: includes/admin/class-wp-job-manager-settings.php:211
477
- msgid "Limits job listing submissions to registered, logged-in users."
 
 
478
  msgstr ""
479
 
480
- #: includes/admin/class-wp-job-manager-settings.php:218
481
  msgid "Account Creation"
482
  msgstr ""
483
 
484
- #: includes/admin/class-wp-job-manager-settings.php:219
485
- msgid "Enable account creation during submission"
486
  msgstr ""
487
 
488
- #: includes/admin/class-wp-job-manager-settings.php:220
489
  msgid ""
490
- "Includes account creation on the listing submission form, to allow "
491
- "non-registered users to create an account and submit a job listing "
492
- "simultaneously."
493
  msgstr ""
494
 
495
- #: includes/admin/class-wp-job-manager-settings.php:227
496
  msgid "Account Username"
497
  msgstr ""
498
 
499
- #: includes/admin/class-wp-job-manager-settings.php:228
500
- msgid "Generate usernames from email addresses"
501
- msgstr ""
502
-
503
- #: includes/admin/class-wp-job-manager-settings.php:229
504
- msgid ""
505
- "Automatically generates usernames for new accounts from the registrant's "
506
- "email address. If this is not enabled, a \"username\" field will display "
507
- "instead."
508
- msgstr ""
509
-
510
- #: includes/admin/class-wp-job-manager-settings.php:236
511
- msgid "Account Password"
512
- msgstr ""
513
-
514
- #: includes/admin/class-wp-job-manager-settings.php:237
515
- msgid "Email new users a link to set a password"
516
  msgstr ""
517
 
518
- #: includes/admin/class-wp-job-manager-settings.php:238
519
  msgid ""
520
- "Sends an email to the user with their username and a link to set their "
521
- "password. If this is not enabled, a \"password\" field will display "
522
- "instead, and their email address won't be verified."
523
  msgstr ""
524
 
525
- #: includes/admin/class-wp-job-manager-settings.php:245
526
  msgid "Account Role"
527
  msgstr ""
528
 
529
- #: includes/admin/class-wp-job-manager-settings.php:246
530
  msgid ""
531
- "Any new accounts created during submission will have this role. If you "
532
- "haven't enabled account creation during submission in the options above, "
533
- "your own method of assigning roles will apply."
534
  msgstr ""
535
 
536
- #: includes/admin/class-wp-job-manager-settings.php:253
537
  msgid "Moderate New Listings"
538
  msgstr ""
539
 
540
- #: includes/admin/class-wp-job-manager-settings.php:254
541
- msgid "Require admin approval of all new listing submissions"
542
  msgstr ""
543
 
544
- #: includes/admin/class-wp-job-manager-settings.php:255
545
- msgid ""
546
- "Sets all new submissions to \"pending.\" They will not appear on your site "
547
- "until an admin approves them."
548
  msgstr ""
549
 
550
- #: includes/admin/class-wp-job-manager-settings.php:262
551
  msgid "Allow Pending Edits"
552
  msgstr ""
553
 
554
- #: includes/admin/class-wp-job-manager-settings.php:263
555
- msgid "Allow editing of pending listings"
556
- msgstr ""
557
-
558
- #: includes/admin/class-wp-job-manager-settings.php:264
559
- msgid ""
560
- "Users can continue to edit pending listings until they are approved by an "
561
- "admin."
562
- msgstr ""
563
-
564
- #: includes/admin/class-wp-job-manager-settings.php:271
565
- msgid "Allow Published Edits"
566
- msgstr ""
567
-
568
- #: includes/admin/class-wp-job-manager-settings.php:272
569
- msgid "Allow editing of published listings"
570
  msgstr ""
571
 
572
- #: includes/admin/class-wp-job-manager-settings.php:273
573
  msgid ""
574
- "Choose whether published job listings can be edited and if edits require "
575
- "admin approval. When moderation is required, the original job listings will "
576
- "be unpublished while edits await admin approval."
577
- msgstr ""
578
-
579
- #: includes/admin/class-wp-job-manager-settings.php:276
580
- msgid "Users cannot edit"
581
- msgstr ""
582
-
583
- #: includes/admin/class-wp-job-manager-settings.php:277
584
- msgid "Users can edit without admin approval"
585
  msgstr ""
586
 
587
- #: includes/admin/class-wp-job-manager-settings.php:278
588
- msgid "Users can edit, but edits require admin approval"
589
- msgstr ""
590
-
591
- #: includes/admin/class-wp-job-manager-settings.php:285
592
  msgid "Listing Duration"
593
  msgstr ""
594
 
595
- #: includes/admin/class-wp-job-manager-settings.php:286
596
  msgid ""
597
- "Listings will display for the set number of days, then expire. Leave this "
598
- "field blank if you don't want listings to have an expiration date."
599
  msgstr ""
600
 
601
- #: includes/admin/class-wp-job-manager-settings.php:292
602
  msgid "Application Method"
603
  msgstr ""
604
 
605
- #: includes/admin/class-wp-job-manager-settings.php:293
606
- msgid ""
607
- "Choose the application method job listers will need to provide. Specify URL "
608
- "or email address only, or allow listers to choose which they prefer."
609
  msgstr ""
610
 
611
- #: includes/admin/class-wp-job-manager-settings.php:296
612
  msgid "Email address or website URL"
613
  msgstr ""
614
 
615
- #: includes/admin/class-wp-job-manager-settings.php:297
616
  msgid "Email addresses only"
617
  msgstr ""
618
 
619
- #: includes/admin/class-wp-job-manager-settings.php:298
620
  msgid "Website URLs only"
621
  msgstr ""
622
 
623
- #: includes/admin/class-wp-job-manager-settings.php:304
624
- msgid "reCAPTCHA"
625
- msgstr ""
626
-
627
- #: includes/admin/class-wp-job-manager-settings.php:308
628
- msgid "Are you human?"
629
- msgstr ""
630
-
631
- #: includes/admin/class-wp-job-manager-settings.php:310
632
- msgid "Field Label"
633
- msgstr ""
634
-
635
- #: includes/admin/class-wp-job-manager-settings.php:311
636
- msgid "The label used for the reCAPTCHA field on forms."
637
- msgstr ""
638
-
639
- #: includes/admin/class-wp-job-manager-settings.php:318
640
- msgid "Site Key"
641
- msgstr ""
642
-
643
- #: includes/admin/class-wp-job-manager-settings.php:320
644
- #. translators: Placeholder %s is URL to set up Google reCAPTCHA API key.
645
- msgid ""
646
- "You can retrieve your site key from <a href=\"%s\">Google's reCAPTCHA admin "
647
- "dashboard</a>."
648
- msgstr ""
649
-
650
- #: includes/admin/class-wp-job-manager-settings.php:327
651
- msgid "Secret Key"
652
- msgstr ""
653
-
654
- #: includes/admin/class-wp-job-manager-settings.php:329
655
- #. translators: Placeholder %s is URL to set up Google reCAPTCHA API key.
656
- msgid ""
657
- "You can retrieve your secret key from <a href=\"%s\">Google's reCAPTCHA "
658
- "admin dashboard</a>."
659
- msgstr ""
660
-
661
- #: includes/admin/class-wp-job-manager-settings.php:335
662
- msgid "Job Submission Form"
663
- msgstr ""
664
-
665
- #: includes/admin/class-wp-job-manager-settings.php:336
666
- msgid "Display a reCAPTCHA field on job submission form."
667
- msgstr ""
668
-
669
- #: includes/admin/class-wp-job-manager-settings.php:337
670
- msgid ""
671
- "This will help prevent bots from submitting job listings. You must have "
672
- "entered a valid site key and secret key above."
673
- msgstr ""
674
-
675
- #: includes/admin/class-wp-job-manager-settings.php:344
676
  msgid "Pages"
677
  msgstr ""
678
 
679
- #: includes/admin/class-wp-job-manager-settings.php:349
680
  msgid "Submit Job Form Page"
681
  msgstr ""
682
 
683
- #: includes/admin/class-wp-job-manager-settings.php:350
684
  msgid ""
685
- "Select the page where you've used the [submit_job_form] shortcode. This "
686
- "lets the plugin know the location of the form."
687
  msgstr ""
688
 
689
- #: includes/admin/class-wp-job-manager-settings.php:356
690
  msgid "Job Dashboard Page"
691
  msgstr ""
692
 
693
- #: includes/admin/class-wp-job-manager-settings.php:357
694
  msgid ""
695
- "Select the page where you've used the [job_dashboard] shortcode. This lets "
696
- "the plugin know the location of the dashboard."
697
  msgstr ""
698
 
699
- #: includes/admin/class-wp-job-manager-settings.php:363
700
  msgid "Job Listings Page"
701
  msgstr ""
702
 
703
- #: includes/admin/class-wp-job-manager-settings.php:364
704
  msgid ""
705
- "Select the page where you've used the [jobs] shortcode. This lets the "
706
- "plugin know the location of the job listings page."
707
  msgstr ""
708
 
709
- #: includes/admin/class-wp-job-manager-settings.php:411
710
  msgid "Settings successfully saved"
711
  msgstr ""
712
 
713
- #: includes/admin/class-wp-job-manager-settings.php:436
714
- msgid "Save Changes"
715
- msgstr ""
716
-
717
- #: includes/admin/class-wp-job-manager-settings.php:644
718
  msgid "--no page--"
719
  msgstr ""
720
 
721
- #: includes/admin/class-wp-job-manager-settings.php:649
722
  msgid "Select a page&hellip;"
723
  msgstr ""
724
 
725
- #: includes/admin/class-wp-job-manager-setup.php:52
 
 
 
 
726
  msgid "Setup"
727
  msgstr ""
728
 
729
- #: includes/admin/class-wp-job-manager-setup.php:205
730
  msgid "WP Job Manager Setup"
731
  msgstr ""
732
 
733
- #: includes/admin/class-wp-job-manager-setup.php:212
734
  msgid "1. Introduction"
735
  msgstr ""
736
 
737
- #: includes/admin/class-wp-job-manager-setup.php:213
738
  msgid "2. Page Setup"
739
  msgstr ""
740
 
741
- #: includes/admin/class-wp-job-manager-setup.php:214
742
  msgid "3. Done"
743
  msgstr ""
744
 
745
- #: includes/admin/class-wp-job-manager-setup.php:219
746
- msgid "Welcome to the Setup Wizard!"
747
  msgstr ""
748
 
749
- #: includes/admin/class-wp-job-manager-setup.php:221
750
- msgid ""
751
- "Thanks for installing <em>WP Job Manager</em>! Let's get your site ready to "
752
- "accept job listings."
753
  msgstr ""
754
 
755
- #: includes/admin/class-wp-job-manager-setup.php:222
756
  msgid ""
757
- "This setup wizard will walk you through the process of creating pages for "
758
- "job submissions, management, and listings."
759
  msgstr ""
760
 
761
- #: includes/admin/class-wp-job-manager-setup.php:226
762
- #. translators: Placeholder %s is the path to WPJM documentation site.
763
  msgid ""
764
- "If you'd prefer to skip this and set up your pages manually, our <a "
765
- "href=\"%s\">documentation</a> will walk you through each step."
 
766
  msgstr ""
767
 
768
- #: includes/admin/class-wp-job-manager-setup.php:236
769
- msgid "Start setup"
770
  msgstr ""
771
 
772
- #: includes/admin/class-wp-job-manager-setup.php:237
773
- msgid "Skip setup. I will set up the plugin manually."
774
  msgstr ""
775
 
776
- #: includes/admin/class-wp-job-manager-setup.php:244
777
  msgid "Page Setup"
778
  msgstr ""
779
 
780
- #: includes/admin/class-wp-job-manager-setup.php:246
781
  msgid ""
782
- "With WP Job Manager, employers and applicants can post, manage, and browse "
783
- "job listings right on your website. Tell us which of these common pages "
784
- "you'd like your site to have and we'll create and configure them for you."
 
785
  msgstr ""
786
 
787
- #: includes/admin/class-wp-job-manager-setup.php:251
788
- #. translators: %1$s is URL to WordPress core shortcode documentation. %2$s is
789
- #. URL to WPJM specific shortcode reference.
790
- msgid ""
791
- "(These pages are created using <a href=\"%1$s\" title=\"What is a "
792
- "shortcode?\" target=\"_blank\" class=\"help-page-link\">shortcodes</a>, \n"
793
- "\t\t\t\t\t\t\t\twhich we take care of in this step. If you'd like to build "
794
- "these pages yourself or want to add one of these options to an existing \n"
795
- "\t\t\t\t\t\t\t\tpage on your site, you can skip this step and take a look "
796
- "at <a href=\"%2$s\" target=\"_blank\" class=\"help-page-link\">shortcode "
797
- "documentation</a> for detailed instructions.)"
798
- msgstr ""
799
-
800
- #: includes/admin/class-wp-job-manager-setup.php:266
801
  msgid "Page Title"
802
  msgstr ""
803
 
804
- #: includes/admin/class-wp-job-manager-setup.php:267
805
  msgid "Page Description"
806
  msgstr ""
807
 
808
- #: includes/admin/class-wp-job-manager-setup.php:268
809
  msgid "Content Shortcode"
810
  msgstr ""
811
 
812
- #: includes/admin/class-wp-job-manager-setup.php:276
 
 
 
 
 
813
  msgid ""
814
- "Creates a page that allows employers to post new jobs directly from a page "
815
- "on your website, instead of requiring them to log in to an admin area. If "
816
- "you'd rather not allow this -- for example, if you want employers to use "
817
- "the admin dashboard only -- you can uncheck this setting."
818
  msgstr ""
819
 
820
- #: includes/admin/class-wp-job-manager-setup.php:284
821
  msgid ""
822
- "Creates a page that allows employers to manage their job listings directly "
823
- "from a page on your website, instead of requiring them to log in to an "
824
- "admin area. If you want to manage all job listings from the admin dashboard "
825
- "only, you can uncheck this setting."
826
  msgstr ""
827
 
828
- #: includes/admin/class-wp-job-manager-setup.php:291
829
- msgid "Creates a page where visitors can browse, search, and filter job listings."
 
830
  msgstr ""
831
 
832
- #: includes/admin/class-wp-job-manager-setup.php:299
833
- msgid "Skip this step"
 
 
 
 
 
 
 
 
834
  msgstr ""
835
 
836
- #: includes/admin/class-wp-job-manager-setup.php:309
837
- msgid "You're ready to start using WP Job Manager!"
 
838
  msgstr ""
839
 
840
- #: includes/admin/class-wp-job-manager-setup.php:311
841
- msgid "Wondering what to do now? Here are some of the most common next steps:"
 
 
842
  msgstr ""
843
 
844
- #: includes/admin/class-wp-job-manager-setup.php:314
845
- msgid "Tweak your settings"
846
  msgstr ""
847
 
848
- #: includes/admin/class-wp-job-manager-setup.php:315
849
- msgid "Add a job using the admin dashboard"
850
  msgstr ""
851
 
852
- #: includes/admin/class-wp-job-manager-setup.php:320
853
- msgid "View submitted job listings"
 
 
854
  msgstr ""
855
 
856
- #: includes/admin/class-wp-job-manager-setup.php:322
857
- msgid "Add job listings to a page using the [jobs] shortcode"
 
 
 
 
858
  msgstr ""
859
 
860
- #: includes/admin/class-wp-job-manager-setup.php:329
861
  msgid "Add a job via the front-end"
862
  msgstr ""
863
 
864
- #: includes/admin/class-wp-job-manager-setup.php:331
865
- msgid "Learn to use the front-end job submission board"
 
 
 
 
 
 
 
 
866
  msgstr ""
867
 
868
- #: includes/admin/class-wp-job-manager-setup.php:338
869
  msgid "View the job dashboard"
870
  msgstr ""
871
 
872
- #: includes/admin/class-wp-job-manager-setup.php:340
873
- msgid "Learn to use the front-end job dashboard"
874
  msgstr ""
875
 
876
- #: includes/admin/class-wp-job-manager-setup.php:349
877
- #. translators: %1$s is the URL to WPJM support documentation; %2$s is the URL
878
- #. to WPJM support forums.
879
  msgid ""
880
- "If you need help, you can find more detail in our \n"
881
- "\t\t\t\t\t\t\t<a href=\"%1$s\">support documentation</a> or post your "
882
- "question on the\n"
883
- "\t\t\t\t\t\t\t<a href=\"%2$s\">WP Job Manager support forums</a>. Happy "
884
- "hiring!"
885
  msgstr ""
886
 
887
- #: includes/admin/class-wp-job-manager-setup.php:360
888
- msgid "Support WP Job Manager's Ongoing Development"
889
  msgstr ""
890
 
891
- #: includes/admin/class-wp-job-manager-setup.php:361
892
  msgid ""
893
- "There are lots of ways you can support open source software projects like "
894
- "this one: contributing code, fixing a bug, assisting with non-English "
895
- "translation, or just telling your friends about WP Job Manager to help "
896
- "spread the word. We appreciate your support!"
897
  msgstr ""
898
 
899
- #: includes/admin/class-wp-job-manager-setup.php:363
900
  msgid "Leave a positive review"
901
  msgstr ""
902
 
903
- #: includes/admin/class-wp-job-manager-setup.php:364
904
  msgid "Contribute a localization"
905
  msgstr ""
906
 
907
- #: includes/admin/class-wp-job-manager-setup.php:365
908
  msgid "Contribute code or report a bug"
909
  msgstr ""
910
 
911
- #: includes/admin/class-wp-job-manager-setup.php:366
912
  msgid "Help other users on the forums"
913
  msgstr ""
914
 
915
- #: includes/admin/class-wp-job-manager-taxonomy-meta.php:78
916
- #: includes/admin/class-wp-job-manager-taxonomy-meta.php:101
917
- #: includes/admin/class-wp-job-manager-taxonomy-meta.php:120
918
- #: includes/rest-api/class-wp-job-manager-models-job-types-custom-fields.php:36
919
- msgid "Employment Type"
920
- msgstr ""
921
-
922
- #: includes/admin/class-wp-job-manager-writepanels.php:58
923
- #: includes/forms/class-wp-job-manager-form-submit-job.php:187
924
  msgid "e.g. \"London\""
925
  msgstr ""
926
 
927
- #: includes/admin/class-wp-job-manager-writepanels.php:59
928
- #: includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php:28
929
  msgid "Leave this blank if the location is not important."
930
  msgstr ""
931
 
932
- #: includes/admin/class-wp-job-manager-writepanels.php:63
933
- #: includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php:32
934
  msgid "Application Email or URL"
935
  msgstr ""
936
 
937
- #: includes/admin/class-wp-job-manager-writepanels.php:64
938
  msgid "URL or email which applicants use to apply"
939
  msgstr ""
940
 
941
- #: includes/admin/class-wp-job-manager-writepanels.php:65
942
  msgid ""
943
  "This field is required for the \"application\" area to appear beneath the "
944
  "listing."
945
  msgstr ""
946
 
947
- #: includes/admin/class-wp-job-manager-writepanels.php:70
948
- #: includes/class-wp-job-manager-data-exporter.php:50
949
- #: includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php:37
950
  msgid "Company Name"
951
  msgstr ""
952
 
953
- #: includes/admin/class-wp-job-manager-writepanels.php:75
954
- #: includes/class-wp-job-manager-data-exporter.php:51
955
- #: includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php:41
956
  msgid "Company Website"
957
  msgstr ""
958
 
959
- #: includes/admin/class-wp-job-manager-writepanels.php:80
960
- #: includes/class-wp-job-manager-data-exporter.php:52
961
- #: includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php:45
962
  msgid "Company Tagline"
963
  msgstr ""
964
 
965
- #: includes/admin/class-wp-job-manager-writepanels.php:81
966
  msgid "Brief description about the company"
967
  msgstr ""
968
 
969
- #: includes/admin/class-wp-job-manager-writepanels.php:85
970
- #: includes/class-wp-job-manager-data-exporter.php:53
971
- #: includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php:49
972
  msgid "Company Twitter"
973
  msgstr ""
974
 
975
- #: includes/admin/class-wp-job-manager-writepanels.php:90
976
- #: includes/class-wp-job-manager-data-exporter.php:54
977
- #: includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php:53
978
  msgid "Company Video"
979
  msgstr ""
980
 
981
- #: includes/admin/class-wp-job-manager-writepanels.php:91
982
  msgid "URL to the company video"
983
  msgstr ""
984
 
985
- #: includes/admin/class-wp-job-manager-writepanels.php:96
986
- #: includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php:57
987
  msgid "Position Filled"
988
  msgstr ""
989
 
990
- #: includes/admin/class-wp-job-manager-writepanels.php:99
991
  msgid "Filled listings will no longer accept applications."
992
  msgstr ""
993
 
994
- #: includes/admin/class-wp-job-manager-writepanels.php:104
995
- #: includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php:64
996
  msgid "Featured Listing"
997
  msgstr ""
998
 
999
- #: includes/admin/class-wp-job-manager-writepanels.php:106
1000
  msgid ""
1001
  "Featured listings will be sticky during searches, and can be styled "
1002
  "differently."
1003
  msgstr ""
1004
 
1005
- #: includes/admin/class-wp-job-manager-writepanels.php:111
1006
- #: includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php:68
1007
  msgid "Listing Expiry Date"
1008
  msgstr ""
1009
 
1010
- #: includes/admin/class-wp-job-manager-writepanels.php:120
1011
- #: includes/class-wp-job-manager-email-notifications.php:288
1012
- #: includes/rest-api/class-wp-job-manager-models-job-listings-custom-fields.php:75
1013
- msgid "Posted by"
1014
  msgstr ""
1015
 
1016
- #: includes/admin/class-wp-job-manager-writepanels.php:163
1017
- #. translators: Placeholder %s is the singular name for a job listing post
1018
- #. type.
1019
- msgid "%s Data"
1020
  msgstr ""
1021
 
1022
- #: includes/admin/class-wp-job-manager-writepanels.php:216
1023
- msgid "Most Used"
1024
  msgstr ""
1025
 
1026
- #: includes/admin/class-wp-job-manager-writepanels.php:282
1027
- #: includes/admin/class-wp-job-manager-writepanels.php:287
1028
- #: includes/admin/class-wp-job-manager-writepanels.php:292
1029
  msgid "Use file"
1030
  msgstr ""
1031
 
1032
- #: includes/admin/class-wp-job-manager-writepanels.php:282
1033
- #: includes/admin/class-wp-job-manager-writepanels.php:287
1034
- #: includes/admin/class-wp-job-manager-writepanels.php:292
1035
  msgid "Upload"
1036
  msgstr ""
1037
 
1038
- #: includes/admin/class-wp-job-manager-writepanels.php:292
1039
  msgid "Add file"
1040
  msgstr ""
1041
 
1042
- #: includes/admin/class-wp-job-manager-writepanels.php:556
1043
  msgid "Guest User"
1044
  msgstr ""
1045
 
1046
- #: includes/admin/class-wp-job-manager-writepanels.php:559
1047
  msgid "Change"
1048
  msgstr ""
1049
 
1050
- #: includes/admin/class-wp-job-manager-writepanels.php:563
1051
  msgid "Enter the ID of the user, or leave blank if submitted by a guest."
1052
  msgstr ""
1053
 
1054
- #: includes/admin/class-wp-job-manager-writepanels.php:629
1055
- #. translators: %1$s is placeholder for singular name of the job listing post
1056
- #. type; %2$s is the intl formatted date the listing was last modified.
1057
- msgid "%1$s was last modified by the user on %2$s."
1058
  msgstr ""
1059
 
1060
- #: includes/admin/views/html-admin-page-addons.php:13
1061
- msgid "More Information &rarr;"
1062
  msgstr ""
1063
 
1064
- #: includes/admin/views/html-admin-page-addons.php:43
1065
- msgid "No add-ons were found."
1066
  msgstr ""
1067
 
1068
- #: includes/class-wp-job-manager-ajax.php:172
1069
- #. translators: Placeholder %d is the number of found search results.
1070
- msgid "Search completed. Found %d matching record."
1071
- msgid_plural "Search completed. Found %d matching records."
1072
- msgstr[0] ""
1073
- msgstr[1] ""
1074
 
1075
- #: includes/class-wp-job-manager-ajax.php:268
1076
- msgid "You must be logged in to upload files using this method."
 
 
1077
  msgstr ""
1078
 
1079
- #. Plugin Name of the plugin/theme
1080
- msgid "WP Job Manager"
1081
  msgstr ""
1082
 
1083
- #: includes/class-wp-job-manager-data-exporter.php:49
1084
- #: includes/class-wp-job-manager-post-types.php:268
1085
- msgid "Company Logo"
1086
  msgstr ""
1087
 
1088
- #: includes/class-wp-job-manager-data-exporter.php:79
1089
- msgid "WP Job Manager User Data"
1090
  msgstr ""
1091
 
1092
- #: includes/class-wp-job-manager-email-notifications.php:223
1093
- msgid "Job title"
 
1094
  msgstr ""
1095
 
1096
- #: includes/class-wp-job-manager-email-notifications.php:243
1097
- #: includes/class-wp-job-manager-post-types.php:152
1098
- #: includes/forms/class-wp-job-manager-form-submit-job.php:191
1099
- msgid "Job type"
1100
  msgstr ""
1101
 
1102
- #: includes/class-wp-job-manager-email-notifications.php:253
1103
  #: includes/class-wp-job-manager-post-types.php:91
1104
- #: includes/forms/class-wp-job-manager-form-submit-job.php:200
1105
- msgid "Job category"
 
1106
  msgstr ""
1107
 
1108
- #: includes/class-wp-job-manager-email-notifications.php:262
1109
- #: includes/forms/class-wp-job-manager-form-submit-job.php:225
1110
- msgid "Company name"
 
1111
  msgstr ""
1112
 
1113
- #: includes/class-wp-job-manager-email-notifications.php:270
1114
- msgid "Company website"
 
1115
  msgstr ""
1116
 
1117
- #: includes/class-wp-job-manager-email-notifications.php:279
1118
- msgid "Listing expires"
 
 
1119
  msgstr ""
1120
 
1121
- #: includes/class-wp-job-manager-email-notifications.php:437
1122
- msgid "Email Notifications"
 
1123
  msgstr ""
1124
 
1125
- #: includes/class-wp-job-manager-email-notifications.php:440
1126
- msgid "Select the email notifications to enable."
 
1127
  msgstr ""
1128
 
1129
- #: includes/class-wp-job-manager-email-notifications.php:580
1130
- msgid "Format"
 
1131
  msgstr ""
1132
 
1133
- #: includes/class-wp-job-manager-email-notifications.php:583
1134
- msgid "Send plain text email"
 
1135
  msgstr ""
1136
 
1137
- #: includes/class-wp-job-manager-email-notifications.php:584
1138
- msgid "Send rich text email"
1139
  msgstr ""
1140
 
1141
- #: includes/class-wp-job-manager-geocode.php:230
1142
- msgid "No results found"
 
1143
  msgstr ""
1144
 
1145
- #: includes/class-wp-job-manager-geocode.php:233
1146
- msgid "Query limit reached"
1147
  msgstr ""
1148
 
1149
- #: includes/class-wp-job-manager-geocode.php:237
1150
- #: includes/class-wp-job-manager-geocode.php:240
1151
- msgid "Geocoding error"
1152
  msgstr ""
1153
 
1154
- #: includes/class-wp-job-manager-install.php:67
1155
- msgid "Employer"
 
1156
  msgstr ""
1157
 
1158
- #: includes/class-wp-job-manager-post-types.php:92
1159
- msgid "Job categories"
1160
- msgstr ""
1161
-
1162
- #: includes/class-wp-job-manager-post-types.php:120
1163
- #: includes/class-wp-job-manager-post-types.php:180
1164
- #: includes/class-wp-job-manager-post-types.php:261
1165
- #. translators: Placeholder %s is the plural label of the job listing category
1166
- #. taxonomy type.
1167
- #. translators: Placeholder %s is the plural label of the job listing job type
1168
- #. taxonomy type.
1169
- #. translators: Placeholder %s is the singular label of the job listing post
1170
- #. type.
1171
- msgid "Search %s"
1172
- msgstr ""
1173
-
1174
- #: includes/class-wp-job-manager-post-types.php:122
1175
- #: includes/class-wp-job-manager-post-types.php:182
1176
- #: includes/class-wp-job-manager-post-types.php:247
1177
- #. translators: Placeholder %s is the plural label of the job listing category
1178
- #. taxonomy type.
1179
- #. translators: Placeholder %s is the plural label of the job listing job type
1180
- #. taxonomy type.
1181
- #. translators: Placeholder %s is the plural label of the job listing post
1182
- #. type.
1183
- msgid "All %s"
1184
- msgstr ""
1185
-
1186
- #: includes/class-wp-job-manager-post-types.php:124
1187
- #: includes/class-wp-job-manager-post-types.php:184
1188
- #: includes/class-wp-job-manager-post-types.php:267
1189
- #. translators: Placeholder %s is the singular label of the job listing
1190
- #. category taxonomy type.
1191
- #. translators: Placeholder %s is the singular label of the job listing job
1192
- #. type taxonomy type.
1193
- #. translators: Placeholder %s is the singular label of the job listing post
1194
- #. type.
1195
- msgid "Parent %s"
1196
- msgstr ""
1197
-
1198
- #: includes/class-wp-job-manager-post-types.php:126
1199
- #: includes/class-wp-job-manager-post-types.php:186
1200
- #. translators: Placeholder %s is the singular label of the job listing
1201
- #. category taxonomy type.
1202
- #. translators: Placeholder %s is the singular label of the job listing job
1203
- #. type taxonomy type.
1204
- msgid "Parent %s:"
1205
- msgstr ""
1206
-
1207
- #: includes/class-wp-job-manager-post-types.php:128
1208
- #: includes/class-wp-job-manager-post-types.php:188
1209
- #: includes/class-wp-job-manager-post-types.php:253
1210
- #. translators: Placeholder %s is the singular label of the job listing
1211
- #. category taxonomy type.
1212
- #. translators: Placeholder %s is the singular label of the job listing job
1213
- #. type taxonomy type.
1214
- #. translators: Placeholder %s is the singular label of the job listing post
1215
- #. type.
1216
- msgid "Edit %s"
1217
- msgstr ""
1218
-
1219
- #: includes/class-wp-job-manager-post-types.php:130
1220
- #: includes/class-wp-job-manager-post-types.php:190
1221
- #. translators: Placeholder %s is the singular label of the job listing
1222
- #. category taxonomy type.
1223
- #. translators: Placeholder %s is the singular label of the job listing job
1224
- #. type taxonomy type.
1225
- msgid "Update %s"
1226
- msgstr ""
1227
-
1228
- #: includes/class-wp-job-manager-post-types.php:132
1229
- #: includes/class-wp-job-manager-post-types.php:192
1230
- #. translators: Placeholder %s is the singular label of the job listing
1231
- #. category taxonomy type.
1232
- #. translators: Placeholder %s is the singular label of the job listing job
1233
- #. type taxonomy type.
1234
- msgid "Add New %s"
1235
- msgstr ""
1236
-
1237
- #: includes/class-wp-job-manager-post-types.php:134
1238
- #: includes/class-wp-job-manager-post-types.php:194
1239
- #. translators: Placeholder %s is the singular label of the job listing
1240
- #. category taxonomy type.
1241
- #. translators: Placeholder %s is the singular label of the job listing job
1242
- #. type taxonomy type.
1243
- msgid "New %s Name"
1244
- msgstr ""
1245
-
1246
- #: includes/class-wp-job-manager-post-types.php:153
1247
- msgid "Job types"
1248
- msgstr ""
1249
-
1250
- #: includes/class-wp-job-manager-post-types.php:214
1251
- msgid "Job"
1252
- msgstr ""
1253
-
1254
- #: includes/class-wp-job-manager-post-types.php:215
1255
- msgid "Jobs"
1256
  msgstr ""
1257
 
1258
- #: includes/class-wp-job-manager-post-types.php:248
1259
  msgid "Add New"
1260
  msgstr ""
1261
 
1262
- #: includes/class-wp-job-manager-post-types.php:250
1263
- #. translators: Placeholder %s is the singular label of the job listing post
1264
- #. type.
1265
  msgid "Add %s"
1266
  msgstr ""
1267
 
1268
- #: includes/class-wp-job-manager-post-types.php:255
1269
- #. translators: Placeholder %s is the singular label of the job listing post
1270
- #. type.
1271
  msgid "New %s"
1272
  msgstr ""
1273
 
1274
- #: includes/class-wp-job-manager-post-types.php:257
1275
- #: includes/class-wp-job-manager-post-types.php:259
1276
- #. translators: Placeholder %s is the singular label of the job listing post
1277
- #. type.
1278
  msgid "View %s"
1279
  msgstr ""
1280
 
1281
- #: includes/class-wp-job-manager-post-types.php:263
1282
- #. translators: Placeholder %s is the singular label of the job listing post
1283
- #. type.
1284
  msgid "No %s found"
1285
  msgstr ""
1286
 
1287
- #: includes/class-wp-job-manager-post-types.php:265
1288
- #. translators: Placeholder %s is the plural label of the job listing post
1289
- #. type.
1290
  msgid "No %s found in trash"
1291
  msgstr ""
1292
 
1293
- #: includes/class-wp-job-manager-post-types.php:269
 
 
 
 
1294
  msgid "Set company logo"
1295
  msgstr ""
1296
 
1297
- #: includes/class-wp-job-manager-post-types.php:270
1298
  msgid "Remove company logo"
1299
  msgstr ""
1300
 
1301
- #: includes/class-wp-job-manager-post-types.php:271
1302
  msgid "Use as company logo"
1303
  msgstr ""
1304
 
1305
- #: includes/class-wp-job-manager-post-types.php:274
1306
- #. translators: Placeholder %s is the plural label of the job listing post
1307
- #. type.
1308
  msgid "This is where you can create and manage %s."
1309
  msgstr ""
1310
 
1311
- #: includes/class-wp-job-manager-post-types.php:310
1312
- #. translators: Placeholder %s is the number of expired posts of this type.
 
 
 
 
 
1313
  msgid "Expired <span class=\"count\">(%s)</span>"
1314
  msgid_plural "Expired <span class=\"count\">(%s)</span>"
1315
  msgstr[0] ""
1316
  msgstr[1] ""
1317
 
1318
- #: includes/class-wp-job-manager-post-types.php:322
1319
- #. translators: Placeholder %s is the number of posts in a preview state.
 
 
 
 
 
1320
  msgid "Preview <span class=\"count\">(%s)</span>"
1321
  msgid_plural "Preview <span class=\"count\">(%s)</span>"
1322
  msgstr[0] ""
1323
  msgstr[1] ""
1324
 
1325
- #: includes/class-wp-job-manager-shortcodes.php:100
1326
  msgid "Invalid ID"
1327
  msgstr ""
1328
 
1329
- #: includes/class-wp-job-manager-shortcodes.php:107
1330
  msgid "This position has already been filled"
1331
  msgstr ""
1332
 
1333
- #: includes/class-wp-job-manager-shortcodes.php:115
1334
- #. translators: Placeholder %s is the job listing title.
1335
  msgid "%s has been filled"
1336
  msgstr ""
1337
 
1338
- #: includes/class-wp-job-manager-shortcodes.php:120
1339
  msgid "This position is not filled"
1340
  msgstr ""
1341
 
1342
- #: includes/class-wp-job-manager-shortcodes.php:128
1343
- #. translators: Placeholder %s is the job listing title.
1344
  msgid "%s has been marked as not filled"
1345
  msgstr ""
1346
 
1347
- #: includes/class-wp-job-manager-shortcodes.php:136
1348
- #. translators: Placeholder %s is the job listing title.
1349
  msgid "%s has been deleted"
1350
  msgstr ""
1351
 
1352
- #: includes/class-wp-job-manager-shortcodes.php:141
1353
- #: includes/class-wp-job-manager-shortcodes.php:154
1354
- msgid "Missing submission page."
1355
- msgstr ""
1356
-
1357
- #: includes/class-wp-job-manager-shortcodes.php:246
1358
- #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:30
1359
- #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:46
1360
- #: includes/widgets/class-wp-job-manager-widget-recent-jobs.php:30
1361
  msgid "Title"
1362
  msgstr ""
1363
 
1364
- #: includes/class-wp-job-manager-shortcodes.php:248
1365
  msgid "Date Posted"
1366
  msgstr ""
1367
 
1368
- #: includes/class-wp-job-manager-shortcodes.php:249
1369
  msgid "Listing Expires"
1370
  msgstr ""
1371
 
1372
- #: includes/class-wp-job-manager-shortcodes.php:384
1373
- #: includes/class-wp-job-manager-shortcodes.php:421
1374
  msgid "Load more listings"
1375
  msgstr ""
1376
 
1377
- #: includes/class-wp-job-manager-usage-tracking.php:108
1378
- #. translators: Placeholder %s is a URL to the document on wpjobmanager.com
1379
- #. with info on usage tracking.
1380
- msgid ""
1381
- "We'd love if you helped us make WP Job Manager better by allowing us to "
1382
- "collect\n"
1383
- "\t\t\t\t<a href=\"%s\" target=\"_blank\">usage tracking data</a>. No "
1384
- "sensitive information is \n"
1385
- "\t\t\t\tcollected, and you can opt out at any time."
1386
- msgstr ""
1387
-
1388
- #: includes/class-wp-job-manager-usage-tracking.php:175
1389
- #. translators: the href tag contains the URL for the page telling users what
1390
- #. data WPJM tracks.
1391
- msgid ""
1392
- "Help us make WP Job Manager better by allowing us to collect\n"
1393
- "\t\t\t\t<a href=\"%s\" target=\"_blank\">usage tracking data</a>.\n"
1394
- "\t\t\t\tNo sensitive information is collected."
1395
- msgstr ""
1396
-
1397
- #: includes/class-wp-job-manager-usage-tracking.php:200
1398
- #: lib/usage-tracking/class-usage-tracking-base.php:475
1399
- msgid "Enable Usage Tracking"
1400
- msgstr ""
1401
-
1402
- #: includes/emails/class-wp-job-manager-email-admin-expiring-job.php:30
1403
- msgid "Admin Notice of Expiring Job Listings"
1404
- msgstr ""
1405
-
1406
- #: includes/emails/class-wp-job-manager-email-admin-expiring-job.php:40
1407
- msgid "Send notices to the site administrator before a job listing expires."
1408
- msgstr ""
1409
-
1410
- #: includes/emails/class-wp-job-manager-email-admin-new-job.php:30
1411
- msgid "Admin Notice of New Listing"
1412
- msgstr ""
1413
-
1414
- #: includes/emails/class-wp-job-manager-email-admin-new-job.php:40
1415
- msgid ""
1416
- "Send a notice to the site administrator when a new job is submitted on the "
1417
- "frontend."
1418
- msgstr ""
1419
-
1420
- #: includes/emails/class-wp-job-manager-email-admin-new-job.php:59
1421
- #. translators: Placeholder %s is the job listing post title.
1422
- msgid "New Job Listing Submitted: %s"
1423
- msgstr ""
1424
-
1425
- #: includes/emails/class-wp-job-manager-email-admin-updated-job.php:30
1426
- msgid "Admin Notice of Updated Listing"
1427
- msgstr ""
1428
-
1429
- #: includes/emails/class-wp-job-manager-email-admin-updated-job.php:40
1430
  msgid ""
1431
- "Send a notice to the site administrator when a job is updated on the "
1432
- "frontend."
1433
- msgstr ""
1434
-
1435
- #: includes/emails/class-wp-job-manager-email-admin-updated-job.php:59
1436
- #. translators: Placeholder %s is the job listing post title.
1437
- msgid "Job Listing Updated: %s"
1438
  msgstr ""
1439
 
1440
- #: includes/emails/class-wp-job-manager-email-employer-expiring-job.php:33
1441
- msgid "Employer Notice of Expiring Job Listings"
 
1442
  msgstr ""
1443
 
1444
- #: includes/emails/class-wp-job-manager-email-employer-expiring-job.php:43
1445
- msgid "Send notices to employers before a job listing expires."
1446
  msgstr ""
1447
 
1448
- #: includes/emails/class-wp-job-manager-email-employer-expiring-job.php:75
1449
- #. translators: Placeholder %s is the job listing post title.
1450
- msgid "Job Listing Expiring: %s"
1451
  msgstr ""
1452
 
1453
- #: includes/emails/class-wp-job-manager-email-employer-expiring-job.php:128
1454
- msgid "Notice Period"
1455
  msgstr ""
1456
 
1457
- #: includes/emails/class-wp-job-manager-email-employer-expiring-job.php:130
1458
- msgid "days"
 
1459
  msgstr ""
1460
 
1461
- #: includes/forms/class-wp-job-manager-form-edit-job.php:97
1462
  msgid "Invalid listing"
1463
  msgstr ""
1464
 
1465
- #: includes/forms/class-wp-job-manager-form-edit-job.php:129
1466
  msgid "Save changes"
1467
  msgstr ""
1468
 
1469
- #: includes/forms/class-wp-job-manager-form-edit-job.php:132
1470
- msgid "Submit changes for approval"
1471
- msgstr ""
1472
-
1473
- #: includes/forms/class-wp-job-manager-form-edit-job.php:183
1474
  msgid "Your changes have been saved."
1475
  msgstr ""
1476
 
1477
- #: includes/forms/class-wp-job-manager-form-edit-job.php:189
1478
  msgid "View &rarr;"
1479
  msgstr ""
1480
 
1481
- #: includes/forms/class-wp-job-manager-form-edit-job.php:191
1482
- msgid ""
1483
- "Your changes have been submitted and your listing will be visible again "
1484
- "once approved."
1485
- msgstr ""
1486
-
1487
- #: includes/forms/class-wp-job-manager-form-submit-job.php:69
1488
  msgid "Submit Details"
1489
  msgstr ""
1490
 
1491
- #: includes/forms/class-wp-job-manager-form-submit-job.php:75
1492
- #: includes/forms/class-wp-job-manager-form-submit-job.php:500
1493
- #: templates/job-preview.php:22
1494
  msgid "Preview"
1495
  msgstr ""
1496
 
1497
- #: includes/forms/class-wp-job-manager-form-submit-job.php:81
1498
  msgid "Done"
1499
  msgstr ""
1500
 
1501
- #: includes/forms/class-wp-job-manager-form-submit-job.php:150
1502
  msgid "Application email"
1503
  msgstr ""
1504
 
1505
- #: includes/forms/class-wp-job-manager-form-submit-job.php:151
1506
- #: wp-job-manager-template.php:717
1507
  msgid "you@yourdomain.com"
1508
  msgstr ""
1509
 
1510
- #: includes/forms/class-wp-job-manager-form-submit-job.php:155
1511
  msgid "Application URL"
1512
  msgstr ""
1513
 
1514
- #: includes/forms/class-wp-job-manager-form-submit-job.php:156
1515
- #: includes/forms/class-wp-job-manager-form-submit-job.php:236
1516
  msgid "http://"
1517
  msgstr ""
1518
 
1519
- #: includes/forms/class-wp-job-manager-form-submit-job.php:160
1520
  msgid "Application email/URL"
1521
  msgstr ""
1522
 
1523
- #: includes/forms/class-wp-job-manager-form-submit-job.php:161
1524
  msgid "Enter an email address or website URL"
1525
  msgstr ""
1526
 
1527
- #: includes/forms/class-wp-job-manager-form-submit-job.php:176
1528
  msgid "Job Title"
1529
  msgstr ""
1530
 
1531
- #: includes/forms/class-wp-job-manager-form-submit-job.php:184
1532
  msgid "Leave this blank if the location is not important"
1533
  msgstr ""
1534
 
1535
- #: includes/forms/class-wp-job-manager-form-submit-job.php:194
1536
- msgid "Choose job type&hellip;"
1537
  msgstr ""
1538
 
1539
- #: includes/forms/class-wp-job-manager-form-submit-job.php:209
1540
- msgid "Description"
1541
  msgstr ""
1542
 
1543
- #: includes/forms/class-wp-job-manager-form-submit-job.php:228
1544
  msgid "Enter the name of the company"
1545
  msgstr ""
1546
 
1547
- #: includes/forms/class-wp-job-manager-form-submit-job.php:232
1548
- #: templates/content-single-job_listing-company.php:30
1549
  msgid "Website"
1550
  msgstr ""
1551
 
1552
- #: includes/forms/class-wp-job-manager-form-submit-job.php:240
1553
  msgid "Tagline"
1554
  msgstr ""
1555
 
1556
- #: includes/forms/class-wp-job-manager-form-submit-job.php:243
1557
  msgid "Briefly describe your company"
1558
  msgstr ""
1559
 
1560
- #: includes/forms/class-wp-job-manager-form-submit-job.php:248
1561
  msgid "Video"
1562
  msgstr ""
1563
 
1564
- #: includes/forms/class-wp-job-manager-form-submit-job.php:252
1565
  msgid "A link to a video about your company"
1566
  msgstr ""
1567
 
1568
- #: includes/forms/class-wp-job-manager-form-submit-job.php:256
1569
  msgid "Twitter username"
1570
  msgstr ""
1571
 
1572
- #: includes/forms/class-wp-job-manager-form-submit-job.php:259
1573
  msgid "@yourcompany"
1574
  msgstr ""
1575
 
1576
- #: includes/forms/class-wp-job-manager-form-submit-job.php:263
1577
  msgid "Logo"
1578
  msgstr ""
1579
 
1580
- #: includes/forms/class-wp-job-manager-form-submit-job.php:313
1581
- #. translators: Placeholder %s is the label for the required field.
1582
  msgid "%s is a required field"
1583
  msgstr ""
1584
 
1585
- #: includes/forms/class-wp-job-manager-form-submit-job.php:324
1586
- #. translators: Placeholder %s is the field label that is did not validate.
1587
  msgid "%s is invalid"
1588
  msgstr ""
1589
 
1590
- #: includes/forms/class-wp-job-manager-form-submit-job.php:341
1591
- #: wp-job-manager-functions.php:1277
1592
- #. translators: Placeholder %1$s is field label; %2$s is the file mime type;
1593
- #. %3$s is the allowed mime-types.
1594
- #. translators: %1$s is the file field label; %2$s is the file type; %3$s is
1595
- #. the list of allowed file types.
1596
- msgid "\"%1$s\" (filetype %2$s) needs to be one of the following file types: %3$s"
1597
  msgstr ""
1598
 
1599
- #: includes/forms/class-wp-job-manager-form-submit-job.php:356
1600
  msgid "Please enter a valid application email address"
1601
  msgstr ""
1602
 
1603
- #: includes/forms/class-wp-job-manager-form-submit-job.php:365
1604
  msgid "Please enter a valid application URL"
1605
  msgstr ""
1606
 
1607
- #: includes/forms/class-wp-job-manager-form-submit-job.php:375
1608
  msgid "Please enter a valid application email address or URL"
1609
  msgstr ""
1610
 
1611
- #: includes/forms/class-wp-job-manager-form-submit-job.php:535
1612
  msgid "Please enter a username."
1613
  msgstr ""
1614
 
1615
- #: includes/forms/class-wp-job-manager-form-submit-job.php:539
1616
- msgid "Please enter a password."
1617
- msgstr ""
1618
-
1619
- #: includes/forms/class-wp-job-manager-form-submit-job.php:543
1620
  msgid "Please enter your email address."
1621
  msgstr ""
1622
 
1623
- #: includes/forms/class-wp-job-manager-form-submit-job.php:549
1624
- msgid "Passwords must match."
1625
- msgstr ""
1626
-
1627
- #: includes/forms/class-wp-job-manager-form-submit-job.php:555
1628
- #. translators: Placeholder %s is the password hint.
1629
- msgid "Invalid Password: %s"
1630
- msgstr ""
1631
-
1632
- #: includes/forms/class-wp-job-manager-form-submit-job.php:557
1633
- msgid "Password is not valid."
1634
- msgstr ""
1635
-
1636
- #: includes/forms/class-wp-job-manager-form-submit-job.php:580
1637
  msgid "You must be signed in to post a new listing."
1638
  msgstr ""
1639
 
1640
- #: includes/helper/class-wp-job-manager-helper.php:265
1641
- msgid "Manage License (Requires Attention)"
1642
- msgstr ""
1643
-
1644
- #: includes/helper/class-wp-job-manager-helper.php:268
1645
- msgid "Manage License"
1646
- msgstr ""
1647
-
1648
- #: includes/helper/class-wp-job-manager-helper.php:271
1649
- #: includes/helper/views/html-licences.php:69
1650
- msgid "Activate License"
1651
- msgstr ""
1652
-
1653
- #: includes/helper/class-wp-job-manager-helper.php:460
1654
- msgid ""
1655
- "Please enter a valid license key and email address in order to activate "
1656
- "this plugin's license."
1657
- msgstr ""
1658
-
1659
- #: includes/helper/class-wp-job-manager-helper.php:490
1660
- msgid "Connection failed to the License Key API server - possible server issue."
1661
- msgstr ""
1662
-
1663
- #: includes/helper/class-wp-job-manager-helper.php:498
1664
- msgid "Plugin license has been activated."
1665
- msgstr ""
1666
-
1667
- #: includes/helper/class-wp-job-manager-helper.php:500
1668
- msgid "An unknown error occurred while attempting to activate the license"
1669
- msgstr ""
1670
-
1671
- #: includes/helper/class-wp-job-manager-helper.php:512
1672
- msgid "license is not active."
1673
- msgstr ""
1674
-
1675
- #: includes/helper/class-wp-job-manager-helper.php:528
1676
- msgid "Plugin license has been deactivated."
1677
- msgstr ""
1678
-
1679
- #: includes/helper/views/html-licence-key-error.php:7
1680
- #: includes/helper/views/html-licence-key-notice.php:7
1681
- msgid "Hide notice"
1682
- msgstr ""
1683
-
1684
- #: includes/helper/views/html-licences.php:50
1685
- #: includes/helper/views/html-licences.php:63
1686
- msgid "License"
1687
- msgstr ""
1688
-
1689
- #: includes/helper/views/html-licences.php:53
1690
- #: includes/helper/views/html-licences.php:66
1691
- msgid "Email"
1692
- msgstr ""
1693
-
1694
- #: includes/helper/views/html-licences.php:57
1695
- msgid "Deactivate License"
1696
- msgstr ""
1697
-
1698
- #: includes/helper/views/html-licences.php:79
1699
- msgid "No plugins are activated that have licenses managed by WP Job Manager."
1700
- msgstr ""
1701
-
1702
- #: includes/rest-api/class-wp-job-manager-controllers-status.php:54
1703
- msgid "Not Found"
1704
- msgstr ""
1705
-
1706
- #: includes/rest-api/class-wp-job-manager-models-job-types-custom-fields.php:54
1707
- msgid "Invalid Employment Type"
1708
- msgstr ""
1709
-
1710
- #: includes/rest-api/class-wp-job-manager-models-settings.php:74
1711
- msgid "Invalid page ID provided"
1712
- msgstr ""
1713
-
1714
- #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:21
1715
- #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:29
1716
- #. translators: Placeholder %s is the plural label for the job listing post
1717
- #. type.
1718
- msgid "Featured %s"
1719
- msgstr ""
1720
-
1721
- #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:23
1722
- msgid "Display a list of featured listings on your site."
1723
- msgstr ""
1724
-
1725
- #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:38
1726
- #: includes/widgets/class-wp-job-manager-widget-recent-jobs.php:48
1727
- msgid "Number of listings to show"
1728
- msgstr ""
1729
-
1730
- #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:43
1731
- msgid "Sort By"
1732
- msgstr ""
1733
-
1734
- #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:45
1735
- msgid "Date"
1736
- msgstr ""
1737
-
1738
- #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:47
1739
- msgid "Author"
1740
- msgstr ""
1741
-
1742
- #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:48
1743
- msgid "Random"
1744
- msgstr ""
1745
-
1746
- #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:54
1747
- msgid "Sort Direction"
1748
- msgstr ""
1749
-
1750
- #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:56
1751
- msgid "Ascending"
1752
- msgstr ""
1753
-
1754
- #: includes/widgets/class-wp-job-manager-widget-featured-jobs.php:57
1755
- msgid "Descending"
1756
- msgstr ""
1757
-
1758
- #: includes/widgets/class-wp-job-manager-widget-recent-jobs.php:21
1759
- #: includes/widgets/class-wp-job-manager-widget-recent-jobs.php:29
1760
- #. translators: Placeholder %s is the plural label for the job listing post
1761
- #. type.
1762
- msgid "Recent %s"
1763
- msgstr ""
1764
-
1765
- #: includes/widgets/class-wp-job-manager-widget-recent-jobs.php:23
1766
- msgid ""
1767
- "Display a list of recent listings on your site, optionally matching a "
1768
- "keyword and location."
1769
- msgstr ""
1770
-
1771
- #: includes/widgets/class-wp-job-manager-widget-recent-jobs.php:35
1772
- msgid "Keyword"
1773
- msgstr ""
1774
-
1775
- #: includes/widgets/class-wp-job-manager-widget-recent-jobs.php:53
1776
- msgid "Show Company Logo"
1777
- msgstr ""
1778
-
1779
- #: lib/usage-tracking/class-usage-tracking-base.php:336
1780
- msgid "Every Two Weeks"
1781
- msgstr ""
1782
-
1783
- #: lib/usage-tracking/class-usage-tracking-base.php:478
1784
- msgid "Disable Usage Tracking"
1785
- msgstr ""
1786
-
1787
- #: lib/usage-tracking/class-usage-tracking-base.php:484
1788
- msgid "Usage data enabled. Thank you!"
1789
- msgstr ""
1790
-
1791
- #: lib/usage-tracking/class-usage-tracking-base.php:487
1792
- msgid "Disabled usage tracking."
1793
- msgstr ""
1794
-
1795
- #: lib/usage-tracking/class-usage-tracking-base.php:490
1796
- msgid "Something went wrong. Please try again later."
1797
- msgstr ""
1798
-
1799
- #: lib/wpjm_rest/class-wp-job-manager-rest-model.php:214
1800
- #. translators: %s is usually a field name.
1801
- msgid "%s cannot be empty"
1802
- msgstr ""
1803
-
1804
- #: lib/wpjm_rest/controller/class-wp-job-manager-rest-controller-crud.php:48
1805
- msgid "Model not found"
1806
- msgstr ""
1807
-
1808
- #: lib/wpjm_rest/controller/class-wp-job-manager-rest-controller-settings.php:35
1809
- msgid "Settings not found"
1810
- msgstr ""
1811
-
1812
- #: templates/account-signin.php:21
1813
  msgid "Your account"
1814
  msgstr ""
1815
 
1816
- #: templates/account-signin.php:25
1817
  msgid "You are currently signed in as <strong>%s</strong>."
1818
  msgstr ""
1819
 
1820
- #: templates/account-signin.php:28
1821
  msgid "Sign out"
1822
  msgstr ""
1823
 
1824
- #: templates/account-signin.php:39
1825
  msgid "Have an account?"
1826
  msgstr ""
1827
 
1828
- #: templates/account-signin.php:41 templates/job-dashboard-login.php:20
1829
  msgid "Sign in"
1830
  msgstr ""
1831
 
1832
- #: templates/account-signin.php:45
1833
  msgid ""
1834
- "If you don't have an account you can %screate one below by entering your "
1835
- "email address/username."
 
1836
  msgstr ""
1837
 
1838
- #: templates/account-signin.php:45
1839
  msgid "optionally"
1840
  msgstr ""
1841
 
1842
- #: templates/account-signin.php:47
1843
- msgid "Your account details will be confirmed via email."
1844
- msgstr ""
1845
-
1846
- #: templates/account-signin.php:52
1847
  msgid "You must sign in to create a new listing."
1848
  msgstr ""
1849
 
1850
- #: templates/account-signin.php:63 templates/job-submit.php:43
1851
- #: templates/job-submit.php:60
1852
- msgid "(optional)"
1853
- msgstr ""
1854
-
1855
- #: templates/content-no-jobs-found.php:20
1856
- msgid "There are no listings matching your search."
1857
- msgstr ""
1858
-
1859
- #: templates/content-no-jobs-found.php:22
1860
- msgid "There are currently no vacancies."
1861
- msgstr ""
1862
-
1863
- #: templates/content-single-job_listing-meta.php:42
1864
- msgid "This position has been filled"
1865
- msgstr ""
1866
-
1867
- #: templates/content-single-job_listing-meta.php:44
1868
- msgid "Applications have closed"
1869
- msgstr ""
1870
-
1871
- #: templates/content-single-job_listing.php:23
1872
- msgid "This listing has expired."
1873
- msgstr ""
1874
-
1875
- #: templates/emails/admin-expiring-job.php:30
1876
- #: templates/emails/employer-expiring-job.php:32
1877
- msgid "The following job listing is expiring today from <a href=\"%s\">%s</a>."
1878
- msgstr ""
1879
-
1880
- #: templates/emails/admin-expiring-job.php:32
1881
- #: templates/emails/employer-expiring-job.php:40
1882
- msgid "The following job listing is expiring soon from <a href=\"%s\">%s</a>."
1883
- msgstr ""
1884
-
1885
- #: templates/emails/admin-expiring-job.php:35
1886
- msgid "Visit <a href=\"%s\">WordPress admin</a> to manage the listing."
1887
- msgstr ""
1888
-
1889
- #: templates/emails/admin-new-job.php:26
1890
- msgid "A new job listing has been submitted to <a href=\"%s\">%s</a>."
1891
- msgstr ""
1892
-
1893
- #: templates/emails/admin-new-job.php:33
1894
- #: templates/emails/plain/admin-new-job.php:26
1895
- msgid "It has been published and is now available to the public."
1896
- msgstr ""
1897
-
1898
- #: templates/emails/admin-new-job.php:38
1899
- msgid ""
1900
- "It is awaiting approval by an administrator in <a href=\"%s\">WordPress "
1901
- "admin</a>."
1902
- msgstr ""
1903
-
1904
- #: templates/emails/admin-updated-job.php:25
1905
- msgid "A job listing has been updated on <a href=\"%s\">%s</a>."
1906
- msgstr ""
1907
-
1908
- #: templates/emails/admin-updated-job.php:28
1909
- #: templates/emails/plain/admin-updated-job.php:26
1910
- msgid "The changes have been published and are now available to the public."
1911
- msgstr ""
1912
-
1913
- #: templates/emails/admin-updated-job.php:32
1914
- msgid ""
1915
- "The job listing is not publicly available until the changes are approved by "
1916
- "an administrator in the site's <a href=\"%s\">WordPress admin</a>."
1917
  msgstr ""
1918
 
1919
- #: templates/emails/employer-expiring-job.php:48
1920
- msgid "Visit the <a href=\"%s\">job listing dashboard</a> to manage the listing."
 
1921
  msgstr ""
1922
 
1923
- #: templates/emails/plain/admin-expiring-job.php:30
1924
- #: templates/emails/plain/employer-expiring-job.php:29
1925
- msgid "The following job listing is expiring today from %s (%s)."
1926
  msgstr ""
1927
 
1928
- #: templates/emails/plain/admin-expiring-job.php:36
1929
- #: templates/emails/plain/employer-expiring-job.php:31
1930
- msgid "The following job listing is expiring soon from %s (%s)."
1931
  msgstr ""
1932
 
1933
- #: templates/emails/plain/admin-expiring-job.php:43
1934
- msgid "Visit WordPress admin (%s) to manage the listing."
1935
  msgstr ""
1936
 
1937
- #: templates/emails/plain/admin-new-job.php:23
1938
- msgid "A new job listing has been submitted to %s (%s)."
1939
  msgstr ""
1940
 
1941
- #: templates/emails/plain/admin-new-job.php:29
1942
- msgid "It is awaiting approval by an administrator in WordPress admin (%s)."
 
1943
  msgstr ""
1944
 
1945
- #: templates/emails/plain/admin-updated-job.php:23
1946
- msgid "A job listing has been updated on %s (%s)."
1947
  msgstr ""
1948
 
1949
- #: templates/emails/plain/admin-updated-job.php:29
1950
- msgid ""
1951
- "The job listing is not publicly available until the changes are approved by "
1952
- "an administrator in the site's WordPress admin (%s)."
1953
  msgstr ""
1954
 
1955
- #: templates/emails/plain/employer-expiring-job.php:33
1956
- msgid "Visit the job listing dashboard (%s) to manage the listing."
1957
  msgstr ""
1958
 
1959
- #: templates/form-fields/file-field.php:45
1960
  msgid "Maximum file size: %s."
1961
  msgstr ""
1962
 
1963
- #: templates/form-fields/multiselect-field.php:20
1964
- #: wp-job-manager-functions.php:1060
1965
  msgid "No results match"
1966
  msgstr ""
1967
 
1968
- #: templates/form-fields/multiselect-field.php:20
1969
- #: wp-job-manager-functions.php:1061
1970
  msgid "Select Some Options"
1971
  msgstr ""
1972
 
1973
- #: templates/form-fields/uploaded-file-html.php:28
1974
- #: templates/form-fields/uploaded-file-html.php:30
1975
  msgid "remove"
1976
  msgstr ""
1977
 
1978
- #: templates/job-application-email.php:18
1979
  msgid ""
1980
- "To apply for this job <strong>email your details to</strong> <a "
1981
- "class=\"job_application_email\" href=\"mailto:%1$s%2$s\">%1$s</a>"
 
 
 
 
1982
  msgstr ""
1983
 
1984
- #: templates/job-application-url.php:18
1985
- msgid "To apply for this job please visit"
1986
  msgstr ""
1987
 
1988
- #: templates/job-application.php:24
1989
  msgid "Apply for job"
1990
  msgstr ""
1991
 
1992
- #: templates/job-dashboard-login.php:20
1993
  msgid "You need to be signed in to manage your listings."
1994
  msgstr ""
1995
 
1996
- #: templates/job-dashboard.php:19
1997
  msgid "Your listings are shown in the table below."
1998
  msgstr ""
1999
 
2000
- #: templates/job-dashboard.php:31
2001
  msgid "You do not have any active listings."
2002
  msgstr ""
2003
 
2004
- #: templates/job-dashboard.php:44
2005
- msgid "Featured Job"
2006
- msgstr ""
2007
-
2008
- #: templates/job-dashboard.php:55
2009
  msgid "Mark not filled"
2010
  msgstr ""
2011
 
2012
- #: templates/job-dashboard.php:57
2013
  msgid "Mark filled"
2014
  msgstr ""
2015
 
2016
- #: templates/job-dashboard.php:60
2017
- msgid "Duplicate"
2018
- msgstr ""
2019
-
2020
- #: templates/job-dashboard.php:64
2021
  msgid "Relist"
2022
  msgstr ""
2023
 
2024
- #: templates/job-filters.php:30 templates/job-filters.php:31
2025
  msgid "Keywords"
2026
  msgstr ""
2027
 
2028
- #: templates/job-filters.php:45
2029
  msgid "Category"
2030
  msgstr ""
2031
 
2032
- #: templates/job-filters.php:49
2033
  msgid "Any category"
2034
  msgstr ""
2035
 
2036
- #: templates/job-filters.php:62
2037
  msgid ""
2038
- "Your browser does not support JavaScript, or it is disabled. JavaScript "
2039
- "must be enabled in order to view listings."
2040
  msgstr ""
2041
 
2042
- #: templates/job-preview.php:20
2043
  msgid "Submit Listing"
2044
  msgstr ""
2045
 
2046
- #: templates/job-preview.php:21
2047
  msgid "Edit listing"
2048
  msgstr ""
2049
 
2050
- #: templates/job-submit.php:24
2051
- msgid "You are editing an existing job. %s"
2052
- msgstr ""
2053
-
2054
- #: templates/job-submit.php:24
2055
- msgid "Create A New Job"
2056
- msgstr ""
2057
-
2058
- #: templates/job-submit.php:54
2059
  msgid "Company Details"
2060
  msgstr ""
2061
 
2062
- #: templates/job-submitted.php:24
2063
- msgid "%s listed successfully. To view your listing <a href=\"%s\">click here</a>."
 
2064
  msgstr ""
2065
 
2066
- #: templates/job-submitted.php:33
2067
  msgid "%s submitted successfully. Your listing will be visible once approved."
2068
  msgstr ""
2069
 
2070
- #: wp-job-manager-functions.php:445
2071
- msgid "Reset"
2072
- msgstr ""
2073
-
2074
- #: wp-job-manager-functions.php:449
2075
- msgid "RSS"
2076
- msgstr ""
2077
-
2078
- #: wp-job-manager-functions.php:554
2079
- msgid "Invalid email address."
2080
- msgstr ""
2081
-
2082
- #: wp-job-manager-functions.php:562
2083
- msgid "Your email address isn&#8217;t correct."
2084
- msgstr ""
2085
-
2086
- #: wp-job-manager-functions.php:566
2087
- msgid "This email is already registered, please choose another one."
2088
- msgstr ""
2089
-
2090
- #: wp-job-manager-functions.php:866
2091
- msgid "Full Time"
2092
  msgstr ""
2093
 
2094
- #: wp-job-manager-functions.php:867
2095
- msgid "Part Time"
 
2096
  msgstr ""
2097
 
2098
- #: wp-job-manager-functions.php:868
2099
- msgid "Contractor"
 
2100
  msgstr ""
2101
 
2102
- #: wp-job-manager-functions.php:869
2103
- msgid "Temporary"
 
2104
  msgstr ""
2105
 
2106
- #: wp-job-manager-functions.php:870
2107
- msgid "Intern"
2108
  msgstr ""
2109
 
2110
- #: wp-job-manager-functions.php:871
2111
- msgid "Volunteer"
2112
  msgstr ""
2113
 
2114
- #: wp-job-manager-functions.php:872
2115
- msgid "Per Diem"
2116
  msgstr ""
2117
 
2118
- #: wp-job-manager-functions.php:873
2119
- msgid "Other"
2120
  msgstr ""
2121
 
2122
- #: wp-job-manager-functions.php:940
2123
- msgid "Passwords must be at least 8 characters long."
2124
  msgstr ""
2125
 
2126
- #: wp-job-manager-functions.php:1059
2127
  msgid "Choose a category&hellip;"
2128
  msgstr ""
2129
 
2130
- #: wp-job-manager-functions.php:1280
2131
- #. translators: %s is the list of allowed file types.
2132
  msgid "Uploaded files need to be one of the following file types: %s"
2133
  msgstr ""
2134
 
2135
- #: wp-job-manager-template.php:153
2136
  msgid "Inactive"
2137
  msgstr ""
2138
 
2139
- #: wp-job-manager-template.php:247
2140
- #. translators: %1$s is the job listing title; %2$s is the URL for the current
2141
- #. WordPress instance.
2142
- msgid "Application via \"%1$s\" listing on %2$s"
2143
- msgstr ""
2144
-
2145
- #: wp-job-manager-template.php:691
2146
- msgid "Username"
2147
- msgstr ""
2148
-
2149
- #: wp-job-manager-template.php:699
2150
- msgid "Password"
2151
- msgstr ""
2152
-
2153
- #: wp-job-manager-template.php:709
2154
- msgid "Verify Password"
2155
- msgstr ""
2156
-
2157
- #: wp-job-manager-template.php:716
2158
- msgid "Your email"
2159
- msgstr ""
2160
-
2161
- #: wp-job-manager-template.php:743
2162
- msgid "Posted on "
2163
- msgstr ""
2164
-
2165
- #: wp-job-manager-template.php:746 wp-job-manager-template.php:767
2166
- #. translators: Placeholder %s is the relative, human readable time since the
2167
- #. job listing was posted.
2168
- msgid "Posted %s ago"
2169
  msgstr ""
2170
 
2171
- #: wp-job-manager-template.php:796
2172
  msgid "Anywhere"
2173
  msgstr ""
2174
 
2175
- #: wp-job-manager.php:168
2176
- #. translators: Placeholders %1$s and %2$s are the names of the two cookies
2177
- #. used in WP Job Manager.
2178
- msgid ""
2179
- "This site adds the following cookies to help users resume job submissions "
2180
- "that they \n"
2181
- "\t\t\t\thave started but have not completed: %1$s and %2$s"
2182
  msgstr ""
2183
 
2184
- #: wp-job-manager.php:332
2185
  msgid "Load previous listings"
2186
  msgstr ""
2187
 
2188
- #: wp-job-manager.php:407
2189
- msgid "Invalid file type. Accepted types:"
2190
  msgstr ""
2191
 
2192
- #: wp-job-manager.php:422
2193
- msgid "Are you sure you want to delete this listing?"
2194
  msgstr ""
2195
 
 
 
 
2196
  #. Author URI of the plugin/theme
2197
  msgid "https://wpjobmanager.com/"
2198
  msgstr ""
@@ -2206,81 +1414,3 @@ msgstr ""
2206
  #. Author of the plugin/theme
2207
  msgid "Automattic"
2208
  msgstr ""
2209
-
2210
- #: includes/admin/class-wp-job-manager-admin.php:139
2211
- #: includes/forms/class-wp-job-manager-form-submit-job.php:413
2212
- #. translators: jQuery date format, see
2213
- #. http:api.jqueryui.com/datepicker/#utility-formatDate
2214
- msgctxt "Date format for jQuery datepicker."
2215
- msgid "yy-mm-dd"
2216
- msgstr ""
2217
-
2218
- #: includes/admin/class-wp-job-manager-permalink-settings.php:86
2219
- #: includes/class-wp-job-manager-post-types.php:701
2220
- msgctxt "Job permalink - resave permalinks after changing this"
2221
- msgid "job"
2222
- msgstr ""
2223
-
2224
- #: includes/admin/class-wp-job-manager-permalink-settings.php:95
2225
- #: includes/class-wp-job-manager-post-types.php:702
2226
- msgctxt "Job category slug - resave permalinks after changing this"
2227
- msgid "job-category"
2228
- msgstr ""
2229
-
2230
- #: includes/admin/class-wp-job-manager-permalink-settings.php:104
2231
- #: includes/class-wp-job-manager-post-types.php:703
2232
- msgctxt "Job type slug - resave permalinks after changing this"
2233
- msgid "job-type"
2234
- msgstr ""
2235
-
2236
- #: includes/admin/class-wp-job-manager-setup.php:274
2237
- msgctxt "Default page title (wizard)"
2238
- msgid "Post a Job"
2239
- msgstr ""
2240
-
2241
- #: includes/admin/class-wp-job-manager-setup.php:282
2242
- msgctxt "Default page title (wizard)"
2243
- msgid "Job Dashboard"
2244
- msgstr ""
2245
-
2246
- #: includes/admin/class-wp-job-manager-setup.php:290
2247
- msgctxt "Default page title (wizard)"
2248
- msgid "Jobs"
2249
- msgstr ""
2250
-
2251
- #: includes/class-wp-job-manager-post-types.php:225
2252
- msgctxt "Post type archive slug - resave permalinks after changing this"
2253
- msgid "jobs"
2254
- msgstr ""
2255
-
2256
- #: includes/class-wp-job-manager-post-types.php:303
2257
- #: wp-job-manager-functions.php:320
2258
- msgctxt "post status"
2259
- msgid "Expired"
2260
- msgstr ""
2261
-
2262
- #: includes/class-wp-job-manager-post-types.php:316
2263
- #: wp-job-manager-functions.php:321
2264
- msgctxt "post status"
2265
- msgid "Preview"
2266
- msgstr ""
2267
-
2268
- #: wp-job-manager-functions.php:319
2269
- msgctxt "post status"
2270
- msgid "Draft"
2271
- msgstr ""
2272
-
2273
- #: wp-job-manager-functions.php:322
2274
- msgctxt "post status"
2275
- msgid "Pending approval"
2276
- msgstr ""
2277
-
2278
- #: wp-job-manager-functions.php:323
2279
- msgctxt "post status"
2280
- msgid "Pending payment"
2281
- msgstr ""
2282
-
2283
- #: wp-job-manager-functions.php:324
2284
- msgctxt "post status"
2285
- msgid "Active"
2286
- msgstr ""
1
+ # Copyright (C) 2016 WP Job Manager
2
+ # This file is distributed under the same license as the WP Job Manager package.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: WP Job Manager 1.24.0\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/WP-Job-Manager\n"
7
+ "POT-Creation-Date: 2016-02-04 12:06:33+00:00\n"
8
  "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=UTF-8\n"
10
  "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2016-MO-DA HO:MI+ZONE\n"
12
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
+ "Language-Team: LANGUAGE <LL@li.org>\n"
 
14
 
15
+ #: includes/admin/class-wp-job-manager-addons.php:57
16
+ #: includes/admin/class-wp-job-manager-admin.php:65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  msgid "WP Job Manager Add-ons"
18
  msgstr ""
19
 
20
+ #: includes/admin/class-wp-job-manager-addons.php:59
21
+ msgid "Do you need multiple add-ons?"
 
22
  msgstr ""
23
 
24
+ #: includes/admin/class-wp-job-manager-addons.php:59
25
+ msgid "Check out the core add-on bundle &rarr;"
 
 
 
 
26
  msgstr ""
27
 
28
+ #: includes/admin/class-wp-job-manager-admin.php:48
29
+ msgctxt "Date format for jQuery datepicker"
30
+ msgid "yy-mm-dd"
 
31
  msgstr ""
32
 
33
+ #: includes/admin/class-wp-job-manager-admin.php:62
34
  msgid "Settings"
35
  msgstr ""
36
 
37
+ #: includes/admin/class-wp-job-manager-admin.php:65
38
  msgid "Add-ons"
39
  msgstr ""
40
 
41
+ #: includes/admin/class-wp-job-manager-cpt.php:48
42
+ #: includes/admin/class-wp-job-manager-cpt.php:49
 
43
  msgid "Approve %s"
44
  msgstr ""
45
 
46
+ #: includes/admin/class-wp-job-manager-cpt.php:51
47
+ #: includes/admin/class-wp-job-manager-cpt.php:52
 
 
 
 
 
 
 
48
  msgid "Expire %s"
49
  msgstr ""
50
 
51
+ #: includes/admin/class-wp-job-manager-cpt.php:140
52
+ #: includes/admin/class-wp-job-manager-cpt.php:142
53
+ msgid "%s approved"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  msgstr ""
55
 
56
+ #: includes/admin/class-wp-job-manager-cpt.php:160
57
+ #: includes/admin/class-wp-job-manager-cpt.php:162
58
+ msgid "%s expired"
 
59
  msgstr ""
60
 
61
+ #: includes/admin/class-wp-job-manager-cpt.php:194
62
  msgid "Select category"
63
  msgstr ""
64
 
65
+ #: includes/admin/class-wp-job-manager-cpt.php:209
66
+ #: includes/admin/class-wp-job-manager-cpt.php:255
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  msgid "Position"
68
  msgstr ""
69
 
70
+ #: includes/admin/class-wp-job-manager-cpt.php:225
71
+ msgid "%s updated. <a href=\"%s\">View</a>"
 
 
72
  msgstr ""
73
 
74
+ #: includes/admin/class-wp-job-manager-cpt.php:226
75
  msgid "Custom field updated."
76
  msgstr ""
77
 
78
+ #: includes/admin/class-wp-job-manager-cpt.php:227
79
  msgid "Custom field deleted."
80
  msgstr ""
81
 
82
+ #: includes/admin/class-wp-job-manager-cpt.php:228
 
83
  msgid "%s updated."
84
  msgstr ""
85
 
86
+ #: includes/admin/class-wp-job-manager-cpt.php:229
87
+ msgid "%s restored to revision from %s"
 
 
88
  msgstr ""
89
 
90
+ #: includes/admin/class-wp-job-manager-cpt.php:230
91
+ msgid "%s published. <a href=\"%s\">View</a>"
 
 
92
  msgstr ""
93
 
94
+ #: includes/admin/class-wp-job-manager-cpt.php:231
 
 
95
  msgid "%s saved."
96
  msgstr ""
97
 
98
+ #: includes/admin/class-wp-job-manager-cpt.php:232
99
+ msgid "%s submitted. <a target=\"_blank\" href=\"%s\">Preview</a>"
 
 
100
  msgstr ""
101
 
102
+ #: includes/admin/class-wp-job-manager-cpt.php:233
 
 
103
  msgid ""
104
+ "%s scheduled for: <strong>%1$s</strong>. <a target=\"_blank\" href=\"%2$s"
105
+ "\">Preview</a>"
106
  msgstr ""
107
 
108
+ #: includes/admin/class-wp-job-manager-cpt.php:234
109
+ msgid "M j, Y @ G:i"
 
 
110
  msgstr ""
111
 
112
+ #: includes/admin/class-wp-job-manager-cpt.php:235
113
+ msgid "%s draft updated. <a target=\"_blank\" href=\"%s\">Preview</a>"
114
+ msgstr ""
115
+
116
+ #: includes/admin/class-wp-job-manager-cpt.php:254
117
  msgid "Type"
118
  msgstr ""
119
 
120
+ #: includes/admin/class-wp-job-manager-cpt.php:256
121
+ #: includes/admin/class-wp-job-manager-writepanels.php:31
122
+ #: includes/class-wp-job-manager-widgets.php:165
123
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:129
124
+ #: templates/job-filters.php:17 templates/job-filters.php:18
 
125
  msgid "Location"
126
  msgstr ""
127
 
128
+ #: includes/admin/class-wp-job-manager-cpt.php:257
129
  msgid "Status"
130
  msgstr ""
131
 
132
+ #: includes/admin/class-wp-job-manager-cpt.php:258
133
  msgid "Posted"
134
  msgstr ""
135
 
136
+ #: includes/admin/class-wp-job-manager-cpt.php:259
137
  msgid "Expires"
138
  msgstr ""
139
 
140
+ #: includes/admin/class-wp-job-manager-cpt.php:260
141
+ #: includes/admin/class-wp-job-manager-settings.php:73
142
  msgid "Categories"
143
  msgstr ""
144
 
145
+ #: includes/admin/class-wp-job-manager-cpt.php:261
146
  msgid "Featured?"
147
  msgstr ""
148
 
149
+ #: includes/admin/class-wp-job-manager-cpt.php:262
150
+ #: includes/class-wp-job-manager-shortcodes.php:163
151
  msgid "Filled?"
152
  msgstr ""
153
 
154
+ #: includes/admin/class-wp-job-manager-cpt.php:263
155
  msgid "Actions"
156
  msgstr ""
157
 
158
+ #: includes/admin/class-wp-job-manager-cpt.php:290
 
159
  msgid "ID: %d"
160
  msgstr ""
161
 
162
+ #: includes/admin/class-wp-job-manager-cpt.php:318
163
+ #: includes/admin/class-wp-job-manager-cpt.php:323
164
+ msgid "M j, Y"
165
+ msgstr ""
166
+
167
+ #: includes/admin/class-wp-job-manager-cpt.php:319
168
  msgid "by a guest"
169
  msgstr ""
170
 
171
+ #: includes/admin/class-wp-job-manager-cpt.php:319
172
  msgid "by %s"
173
  msgstr ""
174
 
175
+ #: includes/admin/class-wp-job-manager-cpt.php:337
176
  msgid "Approve"
177
  msgstr ""
178
 
179
+ #: includes/admin/class-wp-job-manager-cpt.php:345
 
 
 
180
  msgid "View"
181
  msgstr ""
182
 
183
+ #: includes/admin/class-wp-job-manager-cpt.php:352
184
+ #: includes/class-wp-job-manager-post-types.php:185
185
+ #: templates/job-dashboard.php:33 templates/job-dashboard.php:49
186
  msgid "Edit"
187
  msgstr ""
188
 
189
+ #: includes/admin/class-wp-job-manager-cpt.php:359
190
+ #: templates/job-dashboard.php:54
191
  msgid "Delete"
192
  msgstr ""
193
 
194
+ #: includes/admin/class-wp-job-manager-settings.php:42
195
+ #: includes/class-wp-job-manager-post-types.php:181
196
+ #: includes/class-wp-job-manager-post-types.php:248
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  msgid "Job Listings"
198
  msgstr ""
199
 
200
+ #: includes/admin/class-wp-job-manager-settings.php:48
201
  msgid "Listings Per Page"
202
  msgstr ""
203
 
204
+ #: includes/admin/class-wp-job-manager-settings.php:49
205
+ msgid "How many listings should be shown per page by default?"
206
  msgstr ""
207
 
208
+ #: includes/admin/class-wp-job-manager-settings.php:55
209
  msgid "Filled Positions"
210
  msgstr ""
211
 
212
+ #: includes/admin/class-wp-job-manager-settings.php:56
213
  msgid "Hide filled positions"
214
  msgstr ""
215
 
216
+ #: includes/admin/class-wp-job-manager-settings.php:57
217
+ msgid "If enabled, filled positions will be hidden from archives."
 
 
 
 
 
 
 
 
 
 
 
 
218
  msgstr ""
219
 
220
+ #: includes/admin/class-wp-job-manager-settings.php:64
221
+ msgid "Expired Listings"
222
  msgstr ""
223
 
224
+ #: includes/admin/class-wp-job-manager-settings.php:65
225
+ msgid "Hide content within expired listings"
226
  msgstr ""
227
 
228
+ #: includes/admin/class-wp-job-manager-settings.php:66
229
  msgid ""
230
+ "If enabled, the content within expired listings will be hidden. Otherwise, "
231
+ "expired listings will be displayed as normal (without the application area)."
 
232
  msgstr ""
233
 
234
+ #: includes/admin/class-wp-job-manager-settings.php:74
235
+ msgid "Enable categories for listings"
236
  msgstr ""
237
 
238
+ #: includes/admin/class-wp-job-manager-settings.php:75
239
  msgid ""
240
+ "Choose whether to enable categories. Categories must be setup by an admin to "
241
+ "allow users to choose them during submission."
242
  msgstr ""
243
 
244
+ #: includes/admin/class-wp-job-manager-settings.php:82
245
  msgid "Multi-select Categories"
246
  msgstr ""
247
 
248
+ #: includes/admin/class-wp-job-manager-settings.php:83
249
+ msgid "Enable category multiselect by default"
250
  msgstr ""
251
 
252
+ #: includes/admin/class-wp-job-manager-settings.php:84
253
+ #: includes/admin/class-wp-job-manager-settings.php:92
254
  msgid ""
255
+ "If enabled, the category select box will default to a multiselect on the "
256
+ "[jobs] shortcode."
 
257
  msgstr ""
258
 
259
+ #: includes/admin/class-wp-job-manager-settings.php:91
260
  msgid "Category Filter Type"
261
  msgstr ""
262
 
263
+ #: includes/admin/class-wp-job-manager-settings.php:95
 
 
 
 
 
 
264
  msgid "Jobs will be shown if within ANY selected category"
265
  msgstr ""
266
 
267
+ #: includes/admin/class-wp-job-manager-settings.php:96
268
  msgid "Jobs will be shown if within ALL selected categories"
269
  msgstr ""
270
 
271
+ #: includes/admin/class-wp-job-manager-settings.php:102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  msgid "Job Submission"
273
  msgstr ""
274
 
275
+ #: includes/admin/class-wp-job-manager-settings.php:107
276
  msgid "Account Required"
277
  msgstr ""
278
 
279
+ #: includes/admin/class-wp-job-manager-settings.php:108
280
+ msgid "Submitting listings requires an account"
281
  msgstr ""
282
 
283
+ #: includes/admin/class-wp-job-manager-settings.php:109
284
+ msgid ""
285
+ "If disabled, non-logged in users will be able to submit listings without "
286
+ "creating an account."
287
  msgstr ""
288
 
289
+ #: includes/admin/class-wp-job-manager-settings.php:116
290
  msgid "Account Creation"
291
  msgstr ""
292
 
293
+ #: includes/admin/class-wp-job-manager-settings.php:117
294
+ msgid "Allow account creation"
295
  msgstr ""
296
 
297
+ #: includes/admin/class-wp-job-manager-settings.php:118
298
  msgid ""
299
+ "If enabled, non-logged in users will be able to create an account by "
300
+ "entering their email address on the submission form."
 
301
  msgstr ""
302
 
303
+ #: includes/admin/class-wp-job-manager-settings.php:125
304
  msgid "Account Username"
305
  msgstr ""
306
 
307
+ #: includes/admin/class-wp-job-manager-settings.php:126
308
+ msgid "Automatically Generate Username from Email Address"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  msgstr ""
310
 
311
+ #: includes/admin/class-wp-job-manager-settings.php:127
312
  msgid ""
313
+ "If enabled, a username will be generated from the first part of the user "
314
+ "email address. Otherwise, a username field will be shown."
 
315
  msgstr ""
316
 
317
+ #: includes/admin/class-wp-job-manager-settings.php:134
318
  msgid "Account Role"
319
  msgstr ""
320
 
321
+ #: includes/admin/class-wp-job-manager-settings.php:135
322
  msgid ""
323
+ "If you enable registration on your submission form, choose a role for the "
324
+ "new user."
 
325
  msgstr ""
326
 
327
+ #: includes/admin/class-wp-job-manager-settings.php:142
328
  msgid "Moderate New Listings"
329
  msgstr ""
330
 
331
+ #: includes/admin/class-wp-job-manager-settings.php:143
332
+ msgid "New listing submissions require admin approval"
333
  msgstr ""
334
 
335
+ #: includes/admin/class-wp-job-manager-settings.php:144
336
+ msgid "If enabled, new submissions will be inactive, pending admin approval."
 
 
337
  msgstr ""
338
 
339
+ #: includes/admin/class-wp-job-manager-settings.php:151
340
  msgid "Allow Pending Edits"
341
  msgstr ""
342
 
343
+ #: includes/admin/class-wp-job-manager-settings.php:152
344
+ msgid "Submissions awaiting approval can be edited"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  msgstr ""
346
 
347
+ #: includes/admin/class-wp-job-manager-settings.php:153
348
  msgid ""
349
+ "If enabled, submissions awaiting admin approval can be edited by the user."
 
 
 
 
 
 
 
 
 
 
350
  msgstr ""
351
 
352
+ #: includes/admin/class-wp-job-manager-settings.php:160
 
 
 
 
353
  msgid "Listing Duration"
354
  msgstr ""
355
 
356
+ #: includes/admin/class-wp-job-manager-settings.php:161
357
  msgid ""
358
+ "How many <strong>days</strong> listings are live before expiring. Can be "
359
+ "left blank to never expire."
360
  msgstr ""
361
 
362
+ #: includes/admin/class-wp-job-manager-settings.php:167
363
  msgid "Application Method"
364
  msgstr ""
365
 
366
+ #: includes/admin/class-wp-job-manager-settings.php:168
367
+ msgid "Choose the contact method for listings."
 
 
368
  msgstr ""
369
 
370
+ #: includes/admin/class-wp-job-manager-settings.php:171
371
  msgid "Email address or website URL"
372
  msgstr ""
373
 
374
+ #: includes/admin/class-wp-job-manager-settings.php:172
375
  msgid "Email addresses only"
376
  msgstr ""
377
 
378
+ #: includes/admin/class-wp-job-manager-settings.php:173
379
  msgid "Website URLs only"
380
  msgstr ""
381
 
382
+ #: includes/admin/class-wp-job-manager-settings.php:179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  msgid "Pages"
384
  msgstr ""
385
 
386
+ #: includes/admin/class-wp-job-manager-settings.php:184
387
  msgid "Submit Job Form Page"
388
  msgstr ""
389
 
390
+ #: includes/admin/class-wp-job-manager-settings.php:185
391
  msgid ""
392
+ "Select the page where you have placed the [submit_job_form] shortcode. This "
393
+ "lets the plugin know where the form is located."
394
  msgstr ""
395
 
396
+ #: includes/admin/class-wp-job-manager-settings.php:191
397
  msgid "Job Dashboard Page"
398
  msgstr ""
399
 
400
+ #: includes/admin/class-wp-job-manager-settings.php:192
401
  msgid ""
402
+ "Select the page where you have placed the [job_dashboard] shortcode. This "
403
+ "lets the plugin know where the dashboard is located."
404
  msgstr ""
405
 
406
+ #: includes/admin/class-wp-job-manager-settings.php:198
407
  msgid "Job Listings Page"
408
  msgstr ""
409
 
410
+ #: includes/admin/class-wp-job-manager-settings.php:199
411
  msgid ""
412
+ "Select the page where you have placed the [jobs] shortcode. This lets the "
413
+ "plugin know where the job listings page is located."
414
  msgstr ""
415
 
416
+ #: includes/admin/class-wp-job-manager-settings.php:251
417
  msgid "Settings successfully saved"
418
  msgstr ""
419
 
420
+ #: includes/admin/class-wp-job-manager-settings.php:311
 
 
 
 
421
  msgid "--no page--"
422
  msgstr ""
423
 
424
+ #: includes/admin/class-wp-job-manager-settings.php:316
425
  msgid "Select a page&hellip;"
426
  msgstr ""
427
 
428
+ #: includes/admin/class-wp-job-manager-settings.php:362
429
+ msgid "Save Changes"
430
+ msgstr ""
431
+
432
+ #: includes/admin/class-wp-job-manager-setup.php:31
433
  msgid "Setup"
434
  msgstr ""
435
 
436
+ #: includes/admin/class-wp-job-manager-setup.php:128
437
  msgid "WP Job Manager Setup"
438
  msgstr ""
439
 
440
+ #: includes/admin/class-wp-job-manager-setup.php:131
441
  msgid "1. Introduction"
442
  msgstr ""
443
 
444
+ #: includes/admin/class-wp-job-manager-setup.php:132
445
  msgid "2. Page Setup"
446
  msgstr ""
447
 
448
+ #: includes/admin/class-wp-job-manager-setup.php:133
449
  msgid "3. Done"
450
  msgstr ""
451
 
452
+ #: includes/admin/class-wp-job-manager-setup.php:138
453
+ msgid "Setup Wizard Introduction"
454
  msgstr ""
455
 
456
+ #: includes/admin/class-wp-job-manager-setup.php:140
457
+ msgid "Thanks for installing <em>WP Job Manager</em>!"
 
 
458
  msgstr ""
459
 
460
+ #: includes/admin/class-wp-job-manager-setup.php:141
461
  msgid ""
462
+ "This setup wizard will help you get started by creating the pages for job "
463
+ "submission, job management, and listing your jobs."
464
  msgstr ""
465
 
466
+ #: includes/admin/class-wp-job-manager-setup.php:142
 
467
  msgid ""
468
+ "If you want to skip the wizard and setup the pages and shortcodes yourself "
469
+ "manually, the process is still relatively simple. Refer to the "
470
+ "%sdocumentation%s for help."
471
  msgstr ""
472
 
473
+ #: includes/admin/class-wp-job-manager-setup.php:145
474
+ msgid "Continue to page setup"
475
  msgstr ""
476
 
477
+ #: includes/admin/class-wp-job-manager-setup.php:146
478
+ msgid "Skip setup. I will setup the plugin manually"
479
  msgstr ""
480
 
481
+ #: includes/admin/class-wp-job-manager-setup.php:152
482
  msgid "Page Setup"
483
  msgstr ""
484
 
485
+ #: includes/admin/class-wp-job-manager-setup.php:154
486
  msgid ""
487
+ "<em>WP Job Manager</em> includes %1$sshortcodes%2$s which can be used within "
488
+ "your %3$spages%2$s to output content. These can be created for you below. "
489
+ "For more information on the job shortcodes view the %4$sshortcode "
490
+ "documentation%2$s."
491
  msgstr ""
492
 
493
+ #: includes/admin/class-wp-job-manager-setup.php:161
 
 
 
 
 
 
 
 
 
 
 
 
 
494
  msgid "Page Title"
495
  msgstr ""
496
 
497
+ #: includes/admin/class-wp-job-manager-setup.php:162
498
  msgid "Page Description"
499
  msgstr ""
500
 
501
+ #: includes/admin/class-wp-job-manager-setup.php:163
502
  msgid "Content Shortcode"
503
  msgstr ""
504
 
505
+ #: includes/admin/class-wp-job-manager-setup.php:169
506
+ msgctxt "Default page title (wizard)"
507
+ msgid "Post a Job"
508
+ msgstr ""
509
+
510
+ #: includes/admin/class-wp-job-manager-setup.php:171
511
  msgid ""
512
+ "This page allows employers to post jobs to your website from the front-end."
 
 
 
513
  msgstr ""
514
 
515
+ #: includes/admin/class-wp-job-manager-setup.php:173
516
  msgid ""
517
+ "If you do not want to accept submissions from users in this way (for example "
518
+ "you just want to post jobs from the admin dashboard) you can skip creating "
519
+ "this page."
 
520
  msgstr ""
521
 
522
+ #: includes/admin/class-wp-job-manager-setup.php:179
523
+ msgctxt "Default page title (wizard)"
524
+ msgid "Job Dashboard"
525
  msgstr ""
526
 
527
+ #: includes/admin/class-wp-job-manager-setup.php:181
528
+ msgid ""
529
+ "This page allows employers to manage and edit their own jobs from the front-"
530
+ "end."
531
+ msgstr ""
532
+
533
+ #: includes/admin/class-wp-job-manager-setup.php:183
534
+ msgid ""
535
+ "If you plan on managing all listings from the admin dashboard you can skip "
536
+ "creating this page."
537
  msgstr ""
538
 
539
+ #: includes/admin/class-wp-job-manager-setup.php:189
540
+ msgctxt "Default page title (wizard)"
541
+ msgid "Jobs"
542
  msgstr ""
543
 
544
+ #: includes/admin/class-wp-job-manager-setup.php:190
545
+ msgid ""
546
+ "This page allows users to browse, search, and filter job listings on the "
547
+ "front-end of your site."
548
  msgstr ""
549
 
550
+ #: includes/admin/class-wp-job-manager-setup.php:198
551
+ msgid "Skip this step"
552
  msgstr ""
553
 
554
+ #: includes/admin/class-wp-job-manager-setup.php:208
555
+ msgid "All Done!"
556
  msgstr ""
557
 
558
+ #: includes/admin/class-wp-job-manager-setup.php:210
559
+ msgid ""
560
+ "Looks like you're all set to start using the plugin. In case you're "
561
+ "wondering where to go next:"
562
  msgstr ""
563
 
564
+ #: includes/admin/class-wp-job-manager-setup.php:213
565
+ msgid "Tweak the plugin settings"
566
+ msgstr ""
567
+
568
+ #: includes/admin/class-wp-job-manager-setup.php:214
569
+ msgid "Add a job via the back-end"
570
  msgstr ""
571
 
572
+ #: includes/admin/class-wp-job-manager-setup.php:217
573
  msgid "Add a job via the front-end"
574
  msgstr ""
575
 
576
+ #: includes/admin/class-wp-job-manager-setup.php:219
577
+ msgid "Find out more about the front-end job submission form"
578
+ msgstr ""
579
+
580
+ #: includes/admin/class-wp-job-manager-setup.php:223
581
+ msgid "View submitted job listings"
582
+ msgstr ""
583
+
584
+ #: includes/admin/class-wp-job-manager-setup.php:225
585
+ msgid "Add the [jobs] shortcode to a page to list jobs"
586
  msgstr ""
587
 
588
+ #: includes/admin/class-wp-job-manager-setup.php:229
589
  msgid "View the job dashboard"
590
  msgstr ""
591
 
592
+ #: includes/admin/class-wp-job-manager-setup.php:231
593
+ msgid "Find out more about the front-end job dashboard"
594
  msgstr ""
595
 
596
+ #: includes/admin/class-wp-job-manager-setup.php:235
 
 
597
  msgid ""
598
+ "And don't forget, if you need any more help using <em>WP Job Manager</em> "
599
+ "you can consult the %1$sdocumentation%2$s or %3$spost on the forums%2$s!"
 
 
 
600
  msgstr ""
601
 
602
+ #: includes/admin/class-wp-job-manager-setup.php:238
603
+ msgid "Support the Ongoing Development of this Plugin"
604
  msgstr ""
605
 
606
+ #: includes/admin/class-wp-job-manager-setup.php:239
607
  msgid ""
608
+ "There are many ways to support open-source projects such as WP Job Manager, "
609
+ "for example code contribution, translation, or even telling your friends how "
610
+ "awesome the plugin (hopefully) is. Thanks in advance for your support - it "
611
+ "is much appreciated!"
612
  msgstr ""
613
 
614
+ #: includes/admin/class-wp-job-manager-setup.php:241
615
  msgid "Leave a positive review"
616
  msgstr ""
617
 
618
+ #: includes/admin/class-wp-job-manager-setup.php:242
619
  msgid "Contribute a localization"
620
  msgstr ""
621
 
622
+ #: includes/admin/class-wp-job-manager-setup.php:243
623
  msgid "Contribute code or report a bug"
624
  msgstr ""
625
 
626
+ #: includes/admin/class-wp-job-manager-setup.php:244
627
  msgid "Help other users on the forums"
628
  msgstr ""
629
 
630
+ #: includes/admin/class-wp-job-manager-writepanels.php:32
631
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:133
 
 
 
 
 
 
 
632
  msgid "e.g. \"London\""
633
  msgstr ""
634
 
635
+ #: includes/admin/class-wp-job-manager-writepanels.php:33
 
636
  msgid "Leave this blank if the location is not important."
637
  msgstr ""
638
 
639
+ #: includes/admin/class-wp-job-manager-writepanels.php:37
 
640
  msgid "Application Email or URL"
641
  msgstr ""
642
 
643
+ #: includes/admin/class-wp-job-manager-writepanels.php:38
644
  msgid "URL or email which applicants use to apply"
645
  msgstr ""
646
 
647
+ #: includes/admin/class-wp-job-manager-writepanels.php:39
648
  msgid ""
649
  "This field is required for the \"application\" area to appear beneath the "
650
  "listing."
651
  msgstr ""
652
 
653
+ #: includes/admin/class-wp-job-manager-writepanels.php:44
 
 
654
  msgid "Company Name"
655
  msgstr ""
656
 
657
+ #: includes/admin/class-wp-job-manager-writepanels.php:49
 
 
658
  msgid "Company Website"
659
  msgstr ""
660
 
661
+ #: includes/admin/class-wp-job-manager-writepanels.php:54
 
 
662
  msgid "Company Tagline"
663
  msgstr ""
664
 
665
+ #: includes/admin/class-wp-job-manager-writepanels.php:55
666
  msgid "Brief description about the company"
667
  msgstr ""
668
 
669
+ #: includes/admin/class-wp-job-manager-writepanels.php:59
 
 
670
  msgid "Company Twitter"
671
  msgstr ""
672
 
673
+ #: includes/admin/class-wp-job-manager-writepanels.php:64
 
 
674
  msgid "Company Video"
675
  msgstr ""
676
 
677
+ #: includes/admin/class-wp-job-manager-writepanels.php:65
678
  msgid "URL to the company video"
679
  msgstr ""
680
 
681
+ #: includes/admin/class-wp-job-manager-writepanels.php:70
 
682
  msgid "Position Filled"
683
  msgstr ""
684
 
685
+ #: includes/admin/class-wp-job-manager-writepanels.php:73
686
  msgid "Filled listings will no longer accept applications."
687
  msgstr ""
688
 
689
+ #: includes/admin/class-wp-job-manager-writepanels.php:78
 
690
  msgid "Featured Listing"
691
  msgstr ""
692
 
693
+ #: includes/admin/class-wp-job-manager-writepanels.php:80
694
  msgid ""
695
  "Featured listings will be sticky during searches, and can be styled "
696
  "differently."
697
  msgstr ""
698
 
699
+ #: includes/admin/class-wp-job-manager-writepanels.php:84
 
700
  msgid "Listing Expiry Date"
701
  msgstr ""
702
 
703
+ #: includes/admin/class-wp-job-manager-writepanels.php:86
704
+ msgctxt "Date format placeholder"
705
+ msgid "yyyy-mm-dd"
 
706
  msgstr ""
707
 
708
+ #: includes/admin/class-wp-job-manager-writepanels.php:92
709
+ msgid "Posted by"
 
 
710
  msgstr ""
711
 
712
+ #: includes/admin/class-wp-job-manager-writepanels.php:124
713
+ msgid "%s Data"
714
  msgstr ""
715
 
716
+ #: includes/admin/class-wp-job-manager-writepanels.php:153
717
+ #: includes/admin/class-wp-job-manager-writepanels.php:156
718
+ #: includes/admin/class-wp-job-manager-writepanels.php:159
719
  msgid "Use file"
720
  msgstr ""
721
 
722
+ #: includes/admin/class-wp-job-manager-writepanels.php:153
723
+ #: includes/admin/class-wp-job-manager-writepanels.php:156
724
+ #: includes/admin/class-wp-job-manager-writepanels.php:159
725
  msgid "Upload"
726
  msgstr ""
727
 
728
+ #: includes/admin/class-wp-job-manager-writepanels.php:159
729
  msgid "Add file"
730
  msgstr ""
731
 
732
+ #: includes/admin/class-wp-job-manager-writepanels.php:327
733
  msgid "Guest User"
734
  msgstr ""
735
 
736
+ #: includes/admin/class-wp-job-manager-writepanels.php:329
737
  msgid "Change"
738
  msgstr ""
739
 
740
+ #: includes/admin/class-wp-job-manager-writepanels.php:333
741
  msgid "Enter the ID of the user, or leave blank if submitted by a guest."
742
  msgstr ""
743
 
744
+ #: includes/class-wp-job-manager-ajax.php:181
745
+ msgid "located in &ldquo;%s&rdquo;"
 
 
746
  msgstr ""
747
 
748
+ #: includes/class-wp-job-manager-ajax.php:188
749
+ msgid "Showing all %s"
750
  msgstr ""
751
 
752
+ #: includes/class-wp-job-manager-geocode.php:141
753
+ msgid "No results found"
754
  msgstr ""
755
 
756
+ #: includes/class-wp-job-manager-geocode.php:145
757
+ msgid "Query limit reached"
758
+ msgstr ""
 
 
 
759
 
760
+ #: includes/class-wp-job-manager-geocode.php:151
761
+ #: includes/class-wp-job-manager-geocode.php:155
762
+ #: includes/class-wp-job-manager-geocode.php:159
763
+ msgid "Geocoding error"
764
  msgstr ""
765
 
766
+ #: includes/class-wp-job-manager-install.php:58
767
+ msgid "Employer"
768
  msgstr ""
769
 
770
+ #: includes/class-wp-job-manager-post-types.php:65
771
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:146
772
+ msgid "Job category"
773
  msgstr ""
774
 
775
+ #: includes/class-wp-job-manager-post-types.php:66
776
+ msgid "Job categories"
777
  msgstr ""
778
 
779
+ #: includes/class-wp-job-manager-post-types.php:70
780
+ msgctxt "Job category slug - resave permalinks after changing this"
781
+ msgid "job-category"
782
  msgstr ""
783
 
784
+ #: includes/class-wp-job-manager-post-types.php:90
785
+ #: includes/class-wp-job-manager-post-types.php:136
786
+ #: includes/class-wp-job-manager-post-types.php:190
787
+ msgid "Search %s"
788
  msgstr ""
789
 
 
790
  #: includes/class-wp-job-manager-post-types.php:91
791
+ #: includes/class-wp-job-manager-post-types.php:137
792
+ #: includes/class-wp-job-manager-post-types.php:182
793
+ msgid "All %s"
794
  msgstr ""
795
 
796
+ #: includes/class-wp-job-manager-post-types.php:92
797
+ #: includes/class-wp-job-manager-post-types.php:138
798
+ #: includes/class-wp-job-manager-post-types.php:193
799
+ msgid "Parent %s"
800
  msgstr ""
801
 
802
+ #: includes/class-wp-job-manager-post-types.php:93
803
+ #: includes/class-wp-job-manager-post-types.php:139
804
+ msgid "Parent %s:"
805
  msgstr ""
806
 
807
+ #: includes/class-wp-job-manager-post-types.php:94
808
+ #: includes/class-wp-job-manager-post-types.php:140
809
+ #: includes/class-wp-job-manager-post-types.php:186
810
+ msgid "Edit %s"
811
  msgstr ""
812
 
813
+ #: includes/class-wp-job-manager-post-types.php:95
814
+ #: includes/class-wp-job-manager-post-types.php:141
815
+ msgid "Update %s"
816
  msgstr ""
817
 
818
+ #: includes/class-wp-job-manager-post-types.php:96
819
+ #: includes/class-wp-job-manager-post-types.php:142
820
+ msgid "Add New %s"
821
  msgstr ""
822
 
823
+ #: includes/class-wp-job-manager-post-types.php:97
824
+ #: includes/class-wp-job-manager-post-types.php:143
825
+ msgid "New %s Name"
826
  msgstr ""
827
 
828
+ #: includes/class-wp-job-manager-post-types.php:112
829
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:137
830
+ msgid "Job type"
831
  msgstr ""
832
 
833
+ #: includes/class-wp-job-manager-post-types.php:113
834
+ msgid "Job types"
835
  msgstr ""
836
 
837
+ #: includes/class-wp-job-manager-post-types.php:117
838
+ msgctxt "Job type slug - resave permalinks after changing this"
839
+ msgid "job-type"
840
  msgstr ""
841
 
842
+ #: includes/class-wp-job-manager-post-types.php:160
843
+ msgid "Job"
844
  msgstr ""
845
 
846
+ #: includes/class-wp-job-manager-post-types.php:161
847
+ msgid "Jobs"
 
848
  msgstr ""
849
 
850
+ #: includes/class-wp-job-manager-post-types.php:164
851
+ msgctxt "Post type archive slug - resave permalinks after changing this"
852
+ msgid "jobs"
853
  msgstr ""
854
 
855
+ #: includes/class-wp-job-manager-post-types.php:170
856
+ msgctxt "Job permalink - resave permalinks after changing this"
857
+ msgid "job"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
858
  msgstr ""
859
 
860
+ #: includes/class-wp-job-manager-post-types.php:183
861
  msgid "Add New"
862
  msgstr ""
863
 
864
+ #: includes/class-wp-job-manager-post-types.php:184
 
 
865
  msgid "Add %s"
866
  msgstr ""
867
 
868
+ #: includes/class-wp-job-manager-post-types.php:187
 
 
869
  msgid "New %s"
870
  msgstr ""
871
 
872
+ #: includes/class-wp-job-manager-post-types.php:188
873
+ #: includes/class-wp-job-manager-post-types.php:189
 
 
874
  msgid "View %s"
875
  msgstr ""
876
 
877
+ #: includes/class-wp-job-manager-post-types.php:191
 
 
878
  msgid "No %s found"
879
  msgstr ""
880
 
881
+ #: includes/class-wp-job-manager-post-types.php:192
 
 
882
  msgid "No %s found in trash"
883
  msgstr ""
884
 
885
+ #: includes/class-wp-job-manager-post-types.php:194
886
+ msgid "Company Logo"
887
+ msgstr ""
888
+
889
+ #: includes/class-wp-job-manager-post-types.php:195
890
  msgid "Set company logo"
891
  msgstr ""
892
 
893
+ #: includes/class-wp-job-manager-post-types.php:196
894
  msgid "Remove company logo"
895
  msgstr ""
896
 
897
+ #: includes/class-wp-job-manager-post-types.php:197
898
  msgid "Use as company logo"
899
  msgstr ""
900
 
901
+ #: includes/class-wp-job-manager-post-types.php:199
 
 
902
  msgid "This is where you can create and manage %s."
903
  msgstr ""
904
 
905
+ #: includes/class-wp-job-manager-post-types.php:224
906
+ #: wp-job-manager-functions.php:197
907
+ msgctxt "post status"
908
+ msgid "Expired"
909
+ msgstr ""
910
+
911
+ #: includes/class-wp-job-manager-post-types.php:230
912
  msgid "Expired <span class=\"count\">(%s)</span>"
913
  msgid_plural "Expired <span class=\"count\">(%s)</span>"
914
  msgstr[0] ""
915
  msgstr[1] ""
916
 
917
+ #: includes/class-wp-job-manager-post-types.php:233
918
+ #: wp-job-manager-functions.php:198
919
+ msgctxt "post status"
920
+ msgid "Preview"
921
+ msgstr ""
922
+
923
+ #: includes/class-wp-job-manager-post-types.php:238
924
  msgid "Preview <span class=\"count\">(%s)</span>"
925
  msgid_plural "Preview <span class=\"count\">(%s)</span>"
926
  msgstr[0] ""
927
  msgstr[1] ""
928
 
929
+ #: includes/class-wp-job-manager-shortcodes.php:62
930
  msgid "Invalid ID"
931
  msgstr ""
932
 
933
+ #: includes/class-wp-job-manager-shortcodes.php:69
934
  msgid "This position has already been filled"
935
  msgstr ""
936
 
937
+ #: includes/class-wp-job-manager-shortcodes.php:75
 
938
  msgid "%s has been filled"
939
  msgstr ""
940
 
941
+ #: includes/class-wp-job-manager-shortcodes.php:80
942
  msgid "This position is not filled"
943
  msgstr ""
944
 
945
+ #: includes/class-wp-job-manager-shortcodes.php:87
 
946
  msgid "%s has been marked as not filled"
947
  msgstr ""
948
 
949
+ #: includes/class-wp-job-manager-shortcodes.php:94
 
950
  msgid "%s has been deleted"
951
  msgstr ""
952
 
953
+ #: includes/class-wp-job-manager-shortcodes.php:162
954
+ #: includes/class-wp-job-manager-widgets.php:155
955
+ #: includes/class-wp-job-manager-widgets.php:260
 
 
 
 
 
 
956
  msgid "Title"
957
  msgstr ""
958
 
959
+ #: includes/class-wp-job-manager-shortcodes.php:164
960
  msgid "Date Posted"
961
  msgstr ""
962
 
963
+ #: includes/class-wp-job-manager-shortcodes.php:165
964
  msgid "Listing Expires"
965
  msgstr ""
966
 
967
+ #: includes/class-wp-job-manager-shortcodes.php:260
968
+ #: includes/class-wp-job-manager-shortcodes.php:294
969
  msgid "Load more listings"
970
  msgstr ""
971
 
972
+ #: includes/class-wp-job-manager-widgets.php:148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
973
  msgid ""
974
+ "Display a list of recent listings on your site, optionally matching a "
975
+ "keyword and location."
 
 
 
 
 
976
  msgstr ""
977
 
978
+ #: includes/class-wp-job-manager-widgets.php:150
979
+ #: includes/class-wp-job-manager-widgets.php:154
980
+ msgid "Recent %s"
981
  msgstr ""
982
 
983
+ #: includes/class-wp-job-manager-widgets.php:160
984
+ msgid "Keyword"
985
  msgstr ""
986
 
987
+ #: includes/class-wp-job-manager-widgets.php:173
988
+ #: includes/class-wp-job-manager-widgets.php:268
989
+ msgid "Number of listings to show"
990
  msgstr ""
991
 
992
+ #: includes/class-wp-job-manager-widgets.php:253
993
+ msgid "Display a list of featured listings on your site."
994
  msgstr ""
995
 
996
+ #: includes/class-wp-job-manager-widgets.php:255
997
+ #: includes/class-wp-job-manager-widgets.php:259
998
+ msgid "Featured %s"
999
  msgstr ""
1000
 
1001
+ #: includes/forms/class-wp-job-manager-form-edit-job.php:51
1002
  msgid "Invalid listing"
1003
  msgstr ""
1004
 
1005
+ #: includes/forms/class-wp-job-manager-form-edit-job.php:90
1006
  msgid "Save changes"
1007
  msgstr ""
1008
 
1009
+ #: includes/forms/class-wp-job-manager-form-edit-job.php:119
1010
+ #: includes/forms/class-wp-job-manager-form-edit-job.php:122
 
 
 
1011
  msgid "Your changes have been saved."
1012
  msgstr ""
1013
 
1014
+ #: includes/forms/class-wp-job-manager-form-edit-job.php:119
1015
  msgid "View &rarr;"
1016
  msgstr ""
1017
 
1018
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:33
 
 
 
 
 
 
1019
  msgid "Submit Details"
1020
  msgstr ""
1021
 
1022
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:39
1023
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:379
1024
+ #: templates/job-preview.php:5
1025
  msgid "Preview"
1026
  msgstr ""
1027
 
1028
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:45
1029
  msgid "Done"
1030
  msgstr ""
1031
 
1032
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:106
1033
  msgid "Application email"
1034
  msgstr ""
1035
 
1036
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:107
1037
+ #: templates/account-signin.php:49
1038
  msgid "you@yourdomain.com"
1039
  msgstr ""
1040
 
1041
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:110
1042
  msgid "Application URL"
1043
  msgstr ""
1044
 
1045
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:111
1046
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:181
1047
  msgid "http://"
1048
  msgstr ""
1049
 
1050
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:114
1051
  msgid "Application email/URL"
1052
  msgstr ""
1053
 
1054
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:115
1055
  msgid "Enter an email address or website URL"
1056
  msgstr ""
1057
 
1058
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:122
1059
  msgid "Job Title"
1060
  msgstr ""
1061
 
1062
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:130
1063
  msgid "Leave this blank if the location is not important"
1064
  msgstr ""
1065
 
1066
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:155
1067
+ msgid "Description"
1068
  msgstr ""
1069
 
1070
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:171
1071
+ msgid "Company name"
1072
  msgstr ""
1073
 
1074
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:174
1075
  msgid "Enter the name of the company"
1076
  msgstr ""
1077
 
1078
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:178
1079
+ #: templates/content-single-job_listing-company.php:19
1080
  msgid "Website"
1081
  msgstr ""
1082
 
1083
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:185
1084
  msgid "Tagline"
1085
  msgstr ""
1086
 
1087
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:188
1088
  msgid "Briefly describe your company"
1089
  msgstr ""
1090
 
1091
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:193
1092
  msgid "Video"
1093
  msgstr ""
1094
 
1095
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:196
1096
  msgid "A link to a video about your company"
1097
  msgstr ""
1098
 
1099
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:200
1100
  msgid "Twitter username"
1101
  msgstr ""
1102
 
1103
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:203
1104
  msgid "@yourcompany"
1105
  msgstr ""
1106
 
1107
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:207
1108
  msgid "Logo"
1109
  msgstr ""
1110
 
1111
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:238
 
1112
  msgid "%s is a required field"
1113
  msgstr ""
1114
 
1115
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:248
 
1116
  msgid "%s is invalid"
1117
  msgstr ""
1118
 
1119
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:264
1120
+ #: wp-job-manager-functions.php:702
1121
+ msgid "\"%s\" (filetype %s) needs to be one of the following file types: %s"
 
 
 
 
1122
  msgstr ""
1123
 
1124
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:279
1125
  msgid "Please enter a valid application email address"
1126
  msgstr ""
1127
 
1128
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:288
1129
  msgid "Please enter a valid application URL"
1130
  msgstr ""
1131
 
1132
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:298
1133
  msgid "Please enter a valid application email address or URL"
1134
  msgstr ""
1135
 
1136
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:410
1137
  msgid "Please enter a username."
1138
  msgstr ""
1139
 
1140
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:413
 
 
 
 
1141
  msgid "Please enter your email address."
1142
  msgstr ""
1143
 
1144
+ #: includes/forms/class-wp-job-manager-form-submit-job.php:431
 
 
 
 
 
 
 
 
 
 
 
 
 
1145
  msgid "You must be signed in to post a new listing."
1146
  msgstr ""
1147
 
1148
+ #: templates/account-signin.php:4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1149
  msgid "Your account"
1150
  msgstr ""
1151
 
1152
+ #: templates/account-signin.php:8
1153
  msgid "You are currently signed in as <strong>%s</strong>."
1154
  msgstr ""
1155
 
1156
+ #: templates/account-signin.php:11
1157
  msgid "Sign out"
1158
  msgstr ""
1159
 
1160
+ #: templates/account-signin.php:22
1161
  msgid "Have an account?"
1162
  msgstr ""
1163
 
1164
+ #: templates/account-signin.php:24 templates/job-dashboard-login.php:3
1165
  msgid "Sign in"
1166
  msgstr ""
1167
 
1168
+ #: templates/account-signin.php:28
1169
  msgid ""
1170
+ "If you don&rsquo;t have an account you can %screate one below by entering "
1171
+ "your email address/username. Your account details will be confirmed via "
1172
+ "email."
1173
  msgstr ""
1174
 
1175
+ #: templates/account-signin.php:28
1176
  msgid "optionally"
1177
  msgstr ""
1178
 
1179
+ #: templates/account-signin.php:32
 
 
 
 
1180
  msgid "You must sign in to create a new listing."
1181
  msgstr ""
1182
 
1183
+ #: templates/account-signin.php:40
1184
+ msgid "Username"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1185
  msgstr ""
1186
 
1187
+ #: templates/account-signin.php:40 templates/account-signin.php:47
1188
+ #: templates/job-submit.php:24 templates/job-submit.php:41
1189
+ msgid "(optional)"
1190
  msgstr ""
1191
 
1192
+ #: templates/account-signin.php:47
1193
+ msgid "Your email"
 
1194
  msgstr ""
1195
 
1196
+ #: templates/content-job_listing.php:19
1197
+ msgid "%s ago"
 
1198
  msgstr ""
1199
 
1200
+ #: templates/content-no-jobs-found.php:2
1201
+ msgid "There are no listings matching your search."
1202
  msgstr ""
1203
 
1204
+ #: templates/content-no-jobs-found.php:4
1205
+ msgid "There are currently no vacancies."
1206
  msgstr ""
1207
 
1208
+ #: templates/content-single-job_listing-meta.php:20
1209
+ #: templates/content-summary-job_listing.php:14
1210
+ msgid "Posted %s ago"
1211
  msgstr ""
1212
 
1213
+ #: templates/content-single-job_listing-meta.php:23
1214
+ msgid "This position has been filled"
1215
  msgstr ""
1216
 
1217
+ #: templates/content-single-job_listing-meta.php:25
1218
+ msgid "Applications have closed"
 
 
1219
  msgstr ""
1220
 
1221
+ #: templates/content-single-job_listing.php:6
1222
+ msgid "This listing has expired."
1223
  msgstr ""
1224
 
1225
+ #: templates/form-fields/file-field.php:29
1226
  msgid "Maximum file size: %s."
1227
  msgstr ""
1228
 
1229
+ #: templates/form-fields/multiselect-field.php:3
1230
+ #: wp-job-manager-functions.php:533
1231
  msgid "No results match"
1232
  msgstr ""
1233
 
1234
+ #: templates/form-fields/multiselect-field.php:3
1235
+ #: wp-job-manager-functions.php:534
1236
  msgid "Select Some Options"
1237
  msgstr ""
1238
 
1239
+ #: templates/form-fields/uploaded-file-html.php:12
1240
+ #: templates/form-fields/uploaded-file-html.php:14
1241
  msgid "remove"
1242
  msgstr ""
1243
 
1244
+ #: templates/job-application-email.php:1
1245
  msgid ""
1246
+ "To apply for this job <strong>email your details to</strong> <a class="
1247
+ "\"job_application_email\" href=\"mailto:%1$s%2$s\">%1$s</a>"
1248
+ msgstr ""
1249
+
1250
+ #: templates/job-application-email.php:4
1251
+ msgid "Apply using webmail: "
1252
  msgstr ""
1253
 
1254
+ #: templates/job-application-url.php:1
1255
+ msgid "To apply for this job please visit the following URL:"
1256
  msgstr ""
1257
 
1258
+ #: templates/job-application.php:7
1259
  msgid "Apply for job"
1260
  msgstr ""
1261
 
1262
+ #: templates/job-dashboard-login.php:3
1263
  msgid "You need to be signed in to manage your listings."
1264
  msgstr ""
1265
 
1266
+ #: templates/job-dashboard.php:2
1267
  msgid "Your listings are shown in the table below."
1268
  msgstr ""
1269
 
1270
+ #: templates/job-dashboard.php:14
1271
  msgid "You do not have any active listings."
1272
  msgstr ""
1273
 
1274
+ #: templates/job-dashboard.php:36
 
 
 
 
1275
  msgid "Mark not filled"
1276
  msgstr ""
1277
 
1278
+ #: templates/job-dashboard.php:38
1279
  msgid "Mark filled"
1280
  msgstr ""
1281
 
1282
+ #: templates/job-dashboard.php:43
 
 
 
 
1283
  msgid "Relist"
1284
  msgstr ""
1285
 
1286
+ #: templates/job-filters.php:12 templates/job-filters.php:13
1287
  msgid "Keywords"
1288
  msgstr ""
1289
 
1290
+ #: templates/job-filters.php:27
1291
  msgid "Category"
1292
  msgstr ""
1293
 
1294
+ #: templates/job-filters.php:31
1295
  msgid "Any category"
1296
  msgstr ""
1297
 
1298
+ #: templates/job-filters.php:44
1299
  msgid ""
1300
+ "Your browser does not support JavaScript, or it is disabled. JavaScript must "
1301
+ "be enabled in order to view listings."
1302
  msgstr ""
1303
 
1304
+ #: templates/job-preview.php:3
1305
  msgid "Submit Listing"
1306
  msgstr ""
1307
 
1308
+ #: templates/job-preview.php:4
1309
  msgid "Edit listing"
1310
  msgstr ""
1311
 
1312
+ #: templates/job-submit.php:35
 
 
 
 
 
 
 
 
1313
  msgid "Company Details"
1314
  msgstr ""
1315
 
1316
+ #: templates/job-submitted.php:6
1317
+ msgid ""
1318
+ "%s listed successfully. To view your listing <a href=\"%s\">click here</a>."
1319
  msgstr ""
1320
 
1321
+ #: templates/job-submitted.php:9
1322
  msgid "%s submitted successfully. Your listing will be visible once approved."
1323
  msgstr ""
1324
 
1325
+ #: wp-job-manager-functions.php:196
1326
+ msgctxt "post status"
1327
+ msgid "Draft"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1328
  msgstr ""
1329
 
1330
+ #: wp-job-manager-functions.php:199
1331
+ msgctxt "post status"
1332
+ msgid "Pending approval"
1333
  msgstr ""
1334
 
1335
+ #: wp-job-manager-functions.php:200
1336
+ msgctxt "post status"
1337
+ msgid "Pending payment"
1338
  msgstr ""
1339
 
1340
+ #: wp-job-manager-functions.php:201
1341
+ msgctxt "post status"
1342
+ msgid "Active"
1343
  msgstr ""
1344
 
1345
+ #: wp-job-manager-functions.php:286
1346
+ msgid "Reset"
1347
  msgstr ""
1348
 
1349
+ #: wp-job-manager-functions.php:290
1350
+ msgid "RSS"
1351
  msgstr ""
1352
 
1353
+ #: wp-job-manager-functions.php:377
1354
+ msgid "Invalid email address."
1355
  msgstr ""
1356
 
1357
+ #: wp-job-manager-functions.php:385
1358
+ msgid "Your email address isn&#8217;t correct."
1359
  msgstr ""
1360
 
1361
+ #: wp-job-manager-functions.php:389
1362
+ msgid "This email is already registered, please choose another one."
1363
  msgstr ""
1364
 
1365
+ #: wp-job-manager-functions.php:532
1366
  msgid "Choose a category&hellip;"
1367
  msgstr ""
1368
 
1369
+ #: wp-job-manager-functions.php:704
 
1370
  msgid "Uploaded files need to be one of the following file types: %s"
1371
  msgstr ""
1372
 
1373
+ #: wp-job-manager-template.php:135
1374
  msgid "Inactive"
1375
  msgstr ""
1376
 
1377
+ #: wp-job-manager-template.php:222
1378
+ msgid "Application via \"%s\" listing on %s"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1379
  msgstr ""
1380
 
1381
+ #: wp-job-manager-template.php:285
1382
  msgid "Anywhere"
1383
  msgstr ""
1384
 
1385
+ #: wp-job-manager.php:146
1386
+ msgid "Invalid file type. Accepted types:"
 
 
 
 
 
1387
  msgstr ""
1388
 
1389
+ #: wp-job-manager.php:159
1390
  msgid "Load previous listings"
1391
  msgstr ""
1392
 
1393
+ #: wp-job-manager.php:162
1394
+ msgid "Are you sure you want to delete this listing?"
1395
  msgstr ""
1396
 
1397
+ #. Plugin Name of the plugin/theme
1398
+ msgid "WP Job Manager"
1399
  msgstr ""
1400
 
1401
+ #. #-#-#-#-# WP-Job-Manager.pot (WP Job Manager 1.24.0) #-#-#-#-#
1402
+ #. Plugin URI of the plugin/theme
1403
+ #. #-#-#-#-# WP-Job-Manager.pot (WP Job Manager 1.24.0) #-#-#-#-#
1404
  #. Author URI of the plugin/theme
1405
  msgid "https://wpjobmanager.com/"
1406
  msgstr ""
1414
  #. Author of the plugin/theme
1415
  msgid "Automattic"
1416
  msgstr ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/emogrifier/class-emogrifier.php DELETED
@@ -1,1557 +0,0 @@
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
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/usage-tracking/class-usage-tracking-base.php DELETED
@@ -1,581 +0,0 @@
1
- <?php
2
- /**
3
- * Reusable Usage Tracking library. For sending plugin usage data and events to
4
- * Tracks.
5
- **/
6
-
7
- if ( ! defined( 'ABSPATH' ) ) {
8
- exit;
9
- }
10
-
11
- /**
12
- * Usage Tracking class. Please update the prefix to something unique to your
13
- * plugin.
14
- */
15
- abstract class WP_Job_Manager_Usage_Tracking_Base {
16
- const PLUGIN_PREFIX = 'plugin_';
17
-
18
- /*
19
- * Instance variables.
20
- */
21
-
22
- /**
23
- * The name of the option for hiding the Usage Tracking opt-in dialog.
24
- *
25
- * @var string
26
- **/
27
- protected $hide_tracking_opt_in_option_name;
28
-
29
- /**
30
- * The name of the cron job action for regularly logging usage data.
31
- *
32
- * @var string
33
- **/
34
- private $job_name;
35
-
36
- /**
37
- * Callback function for the usage tracking job.
38
- *
39
- * @var array
40
- **/
41
- private $callback;
42
-
43
-
44
- /*
45
- * Class variables.
46
- */
47
-
48
- /**
49
- * Subclass instances.
50
- *
51
- * @var array
52
- **/
53
- private static $instances = array();
54
-
55
- /**
56
- * Gets the singleton instance of this class. Subclasses should implement
57
- * this as follows:
58
- *
59
- * ```
60
- * public static function get_instance() {
61
- * return self::get_instance_for_subclass( get_class() );
62
- * }
63
- * ```
64
- *
65
- * This function cannot be abstract (because it is static) but it *must* be
66
- * implemented by subclasses.
67
- */
68
- public static function get_instance() {
69
- throw new Exception( 'Usage Tracking subclasses must implement get_instance. See class-usage-tracking-base.php' );
70
- }
71
-
72
-
73
- /*
74
- * Abstract methods.
75
- */
76
-
77
-
78
- /**
79
- * Get prefix for actions and strings. Should be unique to this plugin.
80
- *
81
- * @return string The prefix string.
82
- **/
83
- abstract protected function get_prefix();
84
-
85
- /**
86
- * Get the text domain used by this plugin. This class will add some
87
- * strings to be translated.
88
- *
89
- * @return string The text domain string.
90
- **/
91
- abstract protected function get_text_domain();
92
-
93
- /**
94
- * Determine whether usage tracking is enabled.
95
- *
96
- * @return bool true if usage tracking is enabled, false otherwise.
97
- **/
98
- abstract protected function get_tracking_enabled();
99
-
100
- /**
101
- * Set whether usage tracking is enabled.
102
- *
103
- * @param bool $enable true if usage tracking should be enabled, false if
104
- * it should be disabled.
105
- **/
106
- abstract protected function set_tracking_enabled( $enable );
107
-
108
- /**
109
- * Determine whether current user can manage the tracking options.
110
- *
111
- * @return bool true if the current user is allowed to manage the tracking.
112
- * options, false otherwise.
113
- **/
114
- abstract protected function current_user_can_manage_tracking();
115
-
116
- /**
117
- * Get the text to display in the opt-in dialog for users to enable
118
- * tracking. This text should include a link to a page indicating what data
119
- * is being tracked.
120
- *
121
- * @return string the text to display in the opt-in dialog.
122
- **/
123
- abstract protected function opt_in_dialog_text();
124
-
125
- /**
126
- * Checks if we should send an activated plugin's installed version in the
127
- * `system_log` event.
128
- *
129
- * @param string $plugin_slug the plugin slug to check.
130
- *
131
- * @return bool true if we send the version, false if not.
132
- */
133
- abstract protected function do_track_plugin( $plugin_slug );
134
-
135
-
136
- /*
137
- * Initialization.
138
- */
139
-
140
- /**
141
- * Subclasses may override this to add plugin-specific initialization code.
142
- * However, this constructor must be called by the subclass in order to
143
- * properly initialize the Usage Tracking system.
144
- *
145
- * This class is meant to be a singleton, and assumes that the subclass is
146
- * implemented as such. If multiple instances are instantiated, the results
147
- * are undefined.
148
- **/
149
- protected function __construct() {
150
- // Init instance vars.
151
- $this->hide_tracking_opt_in_option_name = $this->get_prefix() . '_usage_tracking_opt_in_hide';
152
- $this->job_name = $this->get_prefix() . '_usage_tracking_send_usage_data';
153
-
154
- // Set up the opt-in dialog.
155
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_script_deps' ) );
156
- add_action( 'admin_footer', array( $this, 'output_opt_in_js' ) );
157
- add_action( 'admin_notices', array( $this, 'maybe_display_tracking_opt_in' ) );
158
- add_action( 'wp_ajax_' . $this->get_prefix() . '_handle_tracking_opt_in', array( $this, 'handle_tracking_opt_in' ) );
159
-
160
- // Set up schedule and action needed for cron job.
161
- add_filter( 'cron_schedules', array( $this, 'add_usage_tracking_two_week_schedule' ) );
162
- add_action( $this->job_name, array( $this, 'send_usage_data' ) );
163
- }
164
-
165
- /**
166
- * Create (if necessary) and return the singleton instance for the given
167
- * subclass.
168
- *
169
- * @param string $subclass the name of the subclass.
170
- * @return object Instance of $subclass.
171
- */
172
- protected static function get_instance_for_subclass( $subclass ) {
173
- if ( ! isset( self::$instances[ $subclass ] ) ) {
174
- self::$instances[ $subclass ] = new $subclass();
175
- }
176
- return self::$instances[ $subclass ];
177
- }
178
-
179
-
180
- /*
181
- * Public methods.
182
- */
183
-
184
- /**
185
- * Set the Usage Data Callback. This callback should return an array of
186
- * data to be logged periodically to Tracks.
187
- *
188
- * @param callable $callback the callback returning the usage data to be logged.
189
- **/
190
- public function set_callback( $callback ) {
191
- $this->callback = $callback;
192
- }
193
-
194
- /**
195
- * Send an event to Tracks if tracking is enabled.
196
- *
197
- * @param string $event The event name. The prefix string will be
198
- * automatically prepended to this, so please supply this string without a
199
- * prefix.
200
- * @param array $properties Event Properties.
201
- * @param null|int $event_timestamp When the event occurred.
202
- *
203
- * @return null|WP_Error
204
- **/
205
- public function send_event( $event, $properties = array(), $event_timestamp = null ) {
206
-
207
- // Only continue if tracking is enabled.
208
- if ( ! $this->is_tracking_enabled() ) {
209
- return false;
210
- }
211
-
212
- $pixel = 'http://pixel.wp.com/t.gif';
213
- $event_name = $this->get_event_prefix() . '_' . $event;
214
- $user = wp_get_current_user();
215
-
216
- if ( null === $event_timestamp ) {
217
- $event_timestamp = time();
218
- }
219
-
220
- $properties['admin_email'] = get_option( 'admin_email' );
221
- $properties['_ut'] = $this->get_event_prefix() . ':site_url';
222
- // Use site URL as the userid to enable usage tracking at the site level.
223
- // Note that we would likely want to use site URL + user ID for userid if we were.
224
- // to ever add event tracking at the user level.
225
- $properties['_ui'] = site_url();
226
- $properties['_ul'] = $user->user_login;
227
- $properties['_en'] = $event_name;
228
- $properties['_ts'] = $event_timestamp . '000';
229
- $properties['_rt'] = round( microtime( true ) * 1000 ); // log time.
230
- $p = array();
231
-
232
- foreach ( $properties as $key => $value ) {
233
- $p[] = rawurlencode( $key ) . '=' . rawurlencode( $value );
234
- }
235
-
236
- $pixel .= '?' . implode( '&', $p ) . '&_=_'; // EOF marker.
237
- $response = wp_remote_get(
238
- $pixel,
239
- array(
240
- 'blocking' => true,
241
- 'timeout' => 1,
242
- 'redirection' => 2,
243
- 'httpversion' => '1.1',
244
- 'user-agent' => $this->get_event_prefix() . '_usage_tracking',
245
- )
246
- );
247
-
248
- if ( is_wp_error( $response ) ) {
249
- return $response;
250
- }
251
-
252
- $code = isset( $response['response']['code'] ) ? $response['response']['code'] : 0;
253
-
254
- if ( 200 !== $code ) {
255
- return new WP_Error( 'request_failed', 'HTTP Request failed', $code );
256
- }
257
-
258
- return true;
259
- }
260
-
261
- /**
262
- * Set up a regular cron job to send usage data. The job will only send
263
- * the data if tracking is enabled, so it is safe to call this function,
264
- * and schedule the job, before the user opts into tracking.
265
- **/
266
- public function schedule_tracking_task() {
267
- if ( ! wp_next_scheduled( $this->job_name ) ) {
268
- wp_schedule_event( time(), $this->get_prefix() . '_usage_tracking_two_weeks', $this->job_name );
269
- }
270
- }
271
-
272
- /**
273
- * Unschedule the job scheduled by schedule_tracking_task if any is
274
- * scheduled. This should be called on plugin deactivation.
275
- **/
276
- public function unschedule_tracking_task() {
277
- if ( wp_next_scheduled( $this->job_name ) ) {
278
- wp_clear_scheduled_hook( $this->job_name );
279
- }
280
- }
281
-
282
- /**
283
- * Check if tracking is enabled.
284
- *
285
- * @return bool true if tracking is enabled, false otherwise.
286
- **/
287
- public function is_tracking_enabled() {
288
- // Defer to the plugin-specific function.
289
- return $this->get_tracking_enabled();
290
- }
291
-
292
- /**
293
- * Call the usage data callback and send the usage data to Tracks. Only
294
- * sends data if tracking is enabled.
295
- **/
296
- public function send_usage_data() {
297
- if ( ! self::is_tracking_enabled() || ! is_callable( $this->callback ) ) {
298
- return;
299
- }
300
-
301
- $usage_data = call_user_func( $this->callback );
302
-
303
- if ( ! is_array( $usage_data ) ) {
304
- return;
305
- }
306
-
307
- self::send_event( 'system_log', $this->get_system_data() );
308
- self::send_event( 'stats_log', $usage_data );
309
- }
310
-
311
-
312
- /**
313
- * Internal methods.
314
- */
315
-
316
- /**
317
- * Get the prefix for the event-related values. By default, this is the
318
- * same prefix used everywhere else, but plugins may override this if
319
- * needed.
320
- */
321
- protected function get_event_prefix() {
322
- return $this->get_prefix();
323
- }
324
-
325
- /**
326
- * Add two week schedule to use for cron job. Should not be called
327
- * externally.
328
- *
329
- * @param array $schedules the existing cron schedules.
330
- * @return array of $schedules.
331
- **/
332
- public function add_usage_tracking_two_week_schedule( $schedules ) {
333
- $day_in_seconds = 86400;
334
- $schedules[ $this->get_prefix() . '_usage_tracking_two_weeks' ] = array(
335
- 'interval' => 15 * $day_in_seconds,
336
- 'display' => esc_html__( 'Every Two Weeks', $this->get_text_domain() ),
337
- );
338
-
339
- return $schedules;
340
- }
341
-
342
- /**
343
- * Collect system data to track.
344
- *
345
- * @return array
346
- */
347
- public function get_system_data() {
348
- global $wp_version;
349
-
350
- /**
351
- * Current active theme.
352
- *
353
- * @var WP_Theme $theme
354
- */
355
- $theme = wp_get_theme();
356
-
357
- $system_data = array();
358
- $system_data['wp_version'] = $wp_version;
359
- $system_data['php_version'] = PHP_VERSION;
360
- $system_data['locale'] = get_locale();
361
- $system_data['multisite'] = is_multisite() ? 1 : 0;
362
- $system_data['active_theme'] = $theme['Name'];
363
- $system_data['active_theme_version'] = $theme['Version'];
364
-
365
- $plugin_data = $this->get_plugin_data();
366
- foreach ( $plugin_data as $plugin_name => $plugin_version ) {
367
- if ( $this->do_track_plugin( $plugin_name ) ) {
368
- $plugin_friendly_name = preg_replace( '/[^a-z0-9]/', '_', $plugin_name );
369
- $plugin_key = self::PLUGIN_PREFIX . $plugin_friendly_name;
370
- $system_data[ $plugin_key ] = $plugin_version;
371
- }
372
- }
373
-
374
- return $system_data;
375
- }
376
-
377
- /**
378
- * Gets a list of activated plugins.
379
- *
380
- * @return array List of plugins. Index is friendly name, value is version.
381
- */
382
- protected function get_plugin_data() {
383
- $plugins = array();
384
- foreach ( $this->get_plugins() as $plugin_basename => $plugin ) {
385
- $plugin_name = $this->get_plugin_name( $plugin_basename );
386
- $plugins[ $plugin_name ] = $plugin['Version'];
387
- }
388
- return $plugins;
389
- }
390
-
391
- /**
392
- * Partial wrapper for for `get_plugins()` function. Filters out non-active plugins.
393
- *
394
- * @return array Key is the plugin file path and the value is an array of the plugin data.
395
- */
396
- protected function get_plugins() {
397
- if ( ! function_exists( 'get_plugins' ) ) {
398
- include_once ABSPATH . 'wp-admin/includes/plugin.php';
399
- }
400
- $plugins = get_plugins();
401
- foreach ( $plugins as $plugin_basename => $plugin_data ) {
402
- if ( ! is_plugin_active( $plugin_basename ) ) {
403
- unset( $plugins[ $plugin_basename ] );
404
- }
405
- }
406
- return $plugins;
407
- }
408
-
409
- /**
410
- * Returns a friendly slug for a plugin.
411
- *
412
- * @param string $basename Plugin basename.
413
- *
414
- * @return string
415
- */
416
- private function get_plugin_name( $basename ) {
417
- $basename = strtolower( $basename );
418
- if ( false === strpos( $basename, '/' ) ) {
419
- return basename( $basename, '.php' );
420
- }
421
- return dirname( $basename );
422
- }
423
-
424
- /**
425
- * Hide the opt-in for enabling usage tracking.
426
- **/
427
- protected function hide_tracking_opt_in() {
428
- update_option( $this->hide_tracking_opt_in_option_name, true );
429
- }
430
-
431
- /**
432
- * Determine whether the opt-in for enabling usage tracking is hidden.
433
- *
434
- * @return bool true if the opt-in is hidden, false otherwise.
435
- **/
436
- protected function is_opt_in_hidden() {
437
- return (bool) get_option( $this->hide_tracking_opt_in_option_name );
438
- }
439
-
440
- /**
441
- * Allowed html tags, used by wp_kses, for the translated opt-in dialog
442
- * text.
443
- *
444
- * @return array the html tags.
445
- **/
446
- protected function opt_in_dialog_text_allowed_html() {
447
- return array(
448
- 'a' => array(
449
- 'href' => array(),
450
- 'title' => array(),
451
- 'target' => array(),
452
- ),
453
- 'em' => array(),
454
- 'strong' => array(),
455
- );
456
- }
457
-
458
- /**
459
- * If needed, display opt-in dialog to enable tracking. Should not be
460
- * called externally.
461
- **/
462
- public function maybe_display_tracking_opt_in() {
463
- $opt_in_hidden = $this->is_opt_in_hidden();
464
- $user_tracking_enabled = $this->is_tracking_enabled();
465
- $can_manage_tracking = $this->current_user_can_manage_tracking();
466
-
467
- if ( ! $user_tracking_enabled && ! $opt_in_hidden && $can_manage_tracking ) { ?>
468
- <div id="<?php echo esc_attr( $this->get_prefix() ); ?>-usage-tracking-notice" class="notice notice-info"
469
- data-nonce="<?php echo esc_attr( wp_create_nonce( 'tracking-opt-in' ) ); ?>">
470
- <p>
471
- <?php echo wp_kses( $this->opt_in_dialog_text(), $this->opt_in_dialog_text_allowed_html() ); ?>
472
- </p>
473
- <p>
474
- <button class="button button-primary" data-enable-tracking="yes">
475
- <?php esc_html_e( 'Enable Usage Tracking', $this->get_text_domain() ); ?>
476
- </button>
477
- <button class="button" data-enable-tracking="no">
478
- <?php esc_html_e( 'Disable Usage Tracking', $this->get_text_domain() ); ?>
479
- </button>
480
- <span id="progress" class="spinner alignleft"></span>
481
- </p>
482
- </div>
483
- <div id="<?php echo esc_attr( $this->get_prefix() ); ?>-usage-tracking-enable-success" class="notice notice-success hidden">
484
- <p><?php esc_html_e( 'Usage data enabled. Thank you!', $this->get_text_domain() ); ?></p>
485
- </div>
486
- <div id="<?php echo esc_attr( $this->get_prefix() ); ?>-usage-tracking-disable-success" class="notice notice-success hidden">
487
- <p><?php esc_html_e( 'Disabled usage tracking.', $this->get_text_domain() ); ?></p>
488
- </div>
489
- <div id="<?php echo esc_attr( $this->get_prefix() ); ?>-usage-tracking-failure" class="notice notice-error hidden">
490
- <p><?php esc_html_e( 'Something went wrong. Please try again later.', $this->get_text_domain() ); ?></p>
491
- </div>
492
- <?php
493
- }
494
- }
495
-
496
- /**
497
- * Handle ajax request from the opt-in dialog. Should not be called
498
- * externally.
499
- **/
500
- public function handle_tracking_opt_in() {
501
- check_ajax_referer( 'tracking-opt-in', 'nonce' );
502
-
503
- if ( ! $this->current_user_can_manage_tracking() ) {
504
- wp_die( '', '', 403 );
505
- }
506
-
507
- $enable_tracking = isset( $_POST['enable_tracking'] ) && '1' === $_POST['enable_tracking'];
508
- $this->set_tracking_enabled( $enable_tracking );
509
- $this->hide_tracking_opt_in();
510
- $this->send_usage_data();
511
- wp_die();
512
- }
513
-
514
- /**
515
- * Ensure that jQuery has been enqueued since the opt-in dialog JS depends
516
- * on it. Should not be called externally.
517
- **/
518
- public function enqueue_script_deps() {
519
- // Ensure jQuery is loaded.
520
- wp_enqueue_script(
521
- $this->get_prefix() . '_usage-tracking-notice', '',
522
- array( 'jquery' ), null, true
523
- );
524
- }
525
-
526
- /**
527
- * Output the JS code to handle the opt-in dialog. Should not be called
528
- * externally.
529
- **/
530
- public function output_opt_in_js() {
531
- ?>
532
- <script type="text/javascript">
533
- (function( prefix ) {
534
- jQuery( document ).ready( function() {
535
- function displayProgressIndicator() {
536
- jQuery( '#' + prefix + '-usage-tracking-notice #progress' ).addClass( 'is-active' );
537
- }
538
-
539
- function displaySuccess( enabledTracking ) {
540
- if ( enabledTracking ) {
541
- jQuery( '#' + prefix + '-usage-tracking-enable-success' ).show();
542
- } else {
543
- jQuery( '#' + prefix + '-usage-tracking-disable-success' ).show();
544
- }
545
- jQuery( '#' + prefix + '-usage-tracking-notice' ).hide();
546
- }
547
-
548
- function displayError() {
549
- jQuery( '#' + prefix + '-usage-tracking-failure' ).show();
550
- jQuery( '#' + prefix + '-usage-tracking-notice' ).hide();
551
- }
552
-
553
- // Handle button clicks.
554
- jQuery( '#' + prefix + '-usage-tracking-notice button' ).click( function( event ) {
555
- event.preventDefault();
556
-
557
- var enableTracking = jQuery( this ).data( 'enable-tracking' ) == 'yes';
558
- var nonce = jQuery( '#' + prefix + '-usage-tracking-notice' ).data( 'nonce' );
559
-
560
- displayProgressIndicator();
561
-
562
- jQuery.ajax( {
563
- type: 'POST',
564
- url: ajaxurl,
565
- data: {
566
- action: prefix + '_handle_tracking_opt_in',
567
- enable_tracking: enableTracking ? 1 : 0,
568
- nonce: nonce,
569
- },
570
- success: function() {
571
- displaySuccess( enableTracking );
572
- },
573
- error: displayError,
574
- } );
575
- });
576
- });
577
- })( "<?php echo esc_js( $this->get_prefix() ); ?>" );
578
- </script>
579
- <?php
580
- }
581
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/usage-tracking/tests/support/class-usage-tracking-test-subclass.php DELETED
@@ -1,67 +0,0 @@
1
- <?php
2
-
3
- require_once dirname( __FILE__ ) . '/../../class-usage-tracking-base.php';
4
-
5
- /**
6
- * Usage Tracking subclass for testing. Please update the superclass name to
7
- * match the one used by your plugin (usage-tracking/class-usage-tracking-base.php).
8
- */
9
- class Usage_Tracking_Test_Subclass extends WP_Job_Manager_Usage_Tracking_Base {
10
-
11
- const TRACKING_ENABLED_OPTION_NAME = 'testing-usage-tracking-enabled';
12
-
13
- public static function get_instance() {
14
- return self::get_instance_for_subclass( get_class() );
15
- }
16
-
17
- public function get_prefix() {
18
- return 'testing';
19
- }
20
-
21
- public function get_text_domain() {
22
- return 'text-domain';
23
- }
24
-
25
- public function get_tracking_enabled() {
26
- return get_option( self::TRACKING_ENABLED_OPTION_NAME ) || false;
27
- }
28
-
29
- public function set_tracking_enabled( $enable ) {
30
- update_option( self::TRACKING_ENABLED_OPTION_NAME, $enable );
31
- }
32
-
33
- public function current_user_can_manage_tracking() {
34
- return current_user_can( 'manage_usage_tracking' );
35
- }
36
-
37
- public function opt_in_dialog_text() {
38
- return 'Please enable Usage Tracking!';
39
- }
40
-
41
- public function do_track_plugin( $plugin_slug ) {
42
- if ( in_array( $plugin_slug, array( 'hello', 'test', 'my-favorite-plugin' ), true ) ) {
43
- return true;
44
- }
45
- return false;
46
- }
47
-
48
- protected function get_plugins() {
49
- return array(
50
- 'Hello.php' => array(
51
- 'Version' => '1.0.0',
52
- ),
53
- 'jetpack/jetpack.php' => array(
54
- 'Version' => '1.1.1',
55
- ),
56
- 'test-dev/test.php' => array(
57
- 'Version' => '1.1.1',
58
- ),
59
- 'test/test.php' => array(
60
- 'Version' => '1.0.0',
61
- ),
62
- 'my-favorite-plugin/my-favorite-plugin.php' => array(
63
- 'Version' => '1.0.0',
64
- ),
65
- );
66
- }
67
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/usage-tracking/tests/support/wp-die-exception.php DELETED
@@ -1,18 +0,0 @@
1
- <?php
2
-
3
- class WP_Die_Exception extends Exception {
4
- private $wp_die_args = null;
5
-
6
- public function set_wp_die_args( $message, $title, $args ) {
7
- $this->wp_die_args = array(
8
- 'message' => $message,
9
- 'title' => $title,
10
- 'args' => $args,
11
- );
12
- }
13
-
14
- public function get_wp_die_args() {
15
- return $this->wp_die_args;
16
- }
17
- }
18
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/usage-tracking/tests/test-class-usage-tracking.php DELETED
@@ -1,527 +0,0 @@
1
- <?php
2
-
3
- include dirname( __FILE__ ) . '/support/class-usage-tracking-test-subclass.php';
4
- include dirname( __FILE__ ) . '/support/wp-die-exception.php';
5
-
6
- // Ensure instance is set up before PHPUnit starts removing hooks.
7
- Usage_Tracking_Test_Subclass::get_instance();
8
-
9
- /**
10
- * Usage Tracking tests. Please update the prefix to something unique to your
11
- * plugin.
12
- *
13
- * @group usage-tracking
14
- */
15
- class WP_Job_Manager_Usage_Tracking_Test extends WP_UnitTestCase {
16
- private $event_counts = array();
17
- private $track_http_request = array();
18
-
19
- public function setUp() {
20
- parent::setUp();
21
- // Update the class name here to match the Usage Tracking class.
22
- $this->usage_tracking = Usage_Tracking_Test_Subclass::get_instance();
23
- $this->usage_tracking->set_callback( array( $this, 'basicDataCallback' ) );
24
- }
25
-
26
- /**
27
- * Ensure cron job action is set up.
28
- *
29
- * @covers {Prefix}_Usage_Tracking::hook
30
- */
31
- public function testCronJobActionAdded() {
32
- $this->assertTrue( !! has_action( $this->usage_tracking->get_prefix() . '_usage_tracking_send_usage_data', array( $this->usage_tracking, 'send_usage_data' ) ) );
33
- }
34
-
35
- /**
36
- * Ensure scheduling function works properly.
37
- *
38
- * @covers {Prefix}_Usage_Tracking::schedule_tracking_task
39
- */
40
- public function testScheduleTrackingTask() {
41
- // Make sure it's cleared initially.
42
- wp_clear_scheduled_hook( $this->usage_tracking->get_prefix() . '_usage_tracking_send_usage_data' );
43
-
44
- // Record how many times the event is scheduled.
45
- $this->event_counts['schedule_event'] = 0;
46
- add_filter( 'schedule_event', array( $this, 'countScheduleEvent' ) );
47
-
48
- // Should successfully schedule the task.
49
- $this->assertFalse( wp_get_schedule( $this->usage_tracking->get_prefix() . '_usage_tracking_send_usage_data' ), 'Not scheduled initial' );
50
- $this->usage_tracking->schedule_tracking_task();
51
- $this->assertNotFalse( wp_get_schedule( $this->usage_tracking->get_prefix() . '_usage_tracking_send_usage_data' ), 'Schedules a job' );
52
- $this->assertEquals( 1, $this->event_counts['schedule_event'], 'Schedules only one job' );
53
-
54
- // Should not duplicate when called again.
55
- $this->usage_tracking->schedule_tracking_task();
56
- $this->assertEquals( 1, $this->event_counts['schedule_event'], 'Does not schedule an additional job' );
57
- }
58
-
59
- /* Test ajax request cases */
60
-
61
- /**
62
- * Ensure ajax hook is set up properly.
63
- *
64
- * @covers {Prefix}_Usage_Tracking::hook
65
- */
66
- public function testAjaxRequestSetup() {
67
- $this->assertTrue( !! has_action( 'wp_ajax_' . $this->usage_tracking->get_prefix() . '_handle_tracking_opt_in', array( $this->usage_tracking, 'handle_tracking_opt_in' ) ) );
68
- }
69
-
70
- /**
71
- * Ensure tracking is enabled through ajax request.
72
- *
73
- * @covers {Prefix}_Usage_Tracking::_handle_tracking_opt_in
74
- */
75
- public function testAjaxRequestEnableTracking() {
76
- $this->setupAjaxRequest();
77
- $_POST['enable_tracking'] = '1';
78
-
79
- $this->assertFalse( !! $this->usage_tracking->is_tracking_enabled(), 'Usage tracking initially disabled' );
80
- $this->assertFalse( !! get_option( $this->usage_tracking->get_prefix() . '_usage_tracking_opt_in_hide' ), 'Dialog initially shown' );
81
-
82
- try {
83
- $this->usage_tracking->handle_tracking_opt_in();
84
- } catch ( WP_Die_Exception $e ) {
85
- $wp_die_args = $e->get_wp_die_args();
86
- $this->assertEquals( array(), $wp_die_args['args'], 'wp_die call has no non-success status' );
87
- }
88
-
89
- $this->assertTrue( $this->usage_tracking->is_tracking_enabled(), 'Usage tracking enabled' );
90
- $this->assertTrue( get_option( $this->usage_tracking->get_prefix() . '_usage_tracking_opt_in_hide' ), 'Dialog hidden' );
91
- }
92
-
93
- /**
94
- * Ensure usage data is sent when tracking is enabled.
95
- *
96
- * @covers {Prefix}_Usage_Tracking::_handle_tracking_opt_in
97
- */
98
- public function testAjaxRequestEnableTrackingSendsData() {
99
- $this->setupAjaxRequest();
100
- $_POST['enable_tracking'] = '1';
101
-
102
- // Count the number of network requests.
103
- $this->event_counts['http_request'] = 0;
104
- add_filter( 'pre_http_request', array( $this, 'countHttpRequest' ) );
105
-
106
- try {
107
- $this->usage_tracking->handle_tracking_opt_in();
108
- } catch ( WP_Die_Exception $e ) {
109
- $wp_die_args = $e->get_wp_die_args();
110
- $this->assertEquals( array(), $wp_die_args['args'], 'wp_die call has no non-success status' );
111
- }
112
-
113
- $this->assertEquals( 2, $this->event_counts['http_request'], 'Data was sent on usage tracking enable' );
114
- }
115
-
116
- /**
117
- * Ensure tracking is disabled through ajax request.
118
- *
119
- * @covers {Prefix}_Usage_Tracking::_handle_tracking_opt_in
120
- */
121
- public function testAjaxRequestDisableTracking() {
122
- $this->setupAjaxRequest();
123
- $_POST['enable_tracking'] = '0';
124
-
125
- $this->assertFalse( !! $this->usage_tracking->is_tracking_enabled(), 'Usage tracking initially disabled' );
126
- $this->assertFalse( !! get_option( $this->usage_tracking->get_prefix() . '_usage_tracking_opt_in_hide' ), 'Dialog initially shown' );
127
-
128
- try {
129
- $this->usage_tracking->handle_tracking_opt_in();
130
- } catch ( WP_Die_Exception $e ) {
131
- $wp_die_args = $e->get_wp_die_args();
132
- $this->assertEquals( array(), $wp_die_args['args'], 'wp_die call has no non-success status' );
133
- }
134
-
135
- $this->assertFalse( !! $this->usage_tracking->is_tracking_enabled(), 'Usage tracking disabled' );
136
- $this->assertTrue( get_option( $this->usage_tracking->get_prefix() . '_usage_tracking_opt_in_hide' ), 'Dialog hidden' );
137
- }
138
-
139
- /**
140
- * Ensure ajax request fails on nonce failure and does not update option.
141
- *
142
- * @covers {Prefix}_Usage_Tracking::_handle_tracking_opt_in
143
- */
144
- public function testAjaxRequestFailedNonce() {
145
- $this->setupAjaxRequest();
146
- $_REQUEST['nonce'] = 'invalid_nonce_1234';
147
-
148
- $this->assertFalse( !! $this->usage_tracking->is_tracking_enabled(), 'Usage tracking initially disabled' );
149
- $this->assertFalse( !! get_option( $this->usage_tracking->get_prefix() . '_usage_tracking_opt_in_hide' ), 'Dialog initially shown' );
150
-
151
- try {
152
- $this->usage_tracking->handle_tracking_opt_in();
153
- } catch ( WP_Die_Exception $e ) {
154
- $wp_die_args = $e->get_wp_die_args();
155
- $this->assertEquals( 403, $wp_die_args['args']['response'], 'wp_die called has "Forbidden" status' );
156
- }
157
-
158
- $this->assertFalse( !! $this->usage_tracking->is_tracking_enabled(), 'Usage tracking disabled' );
159
- $this->assertFalse( !! get_option( $this->usage_tracking->get_prefix() . '_usage_tracking_opt_in_hide' ), 'Dialog not hidden' );
160
- }
161
-
162
- /**
163
- * Ensure ajax request fails on authorization failure and does not update option.
164
- *
165
- * @covers {Prefix}_Usage_Tracking::_handle_tracking_opt_in
166
- */
167
- public function testAjaxRequestFailedAuth() {
168
- $this->setupAjaxRequest();
169
-
170
- // Current user cannot enable tracking.
171
- $this->allowCurrentUserToEnableTracking( false );
172
-
173
- $this->assertFalse( !! $this->usage_tracking->is_tracking_enabled(), 'Usage tracking initially disabled' );
174
- $this->assertFalse( !! get_option( $this->usage_tracking->get_prefix() . '_usage_tracking_opt_in_hide' ), 'Dialog initially shown' );
175
-
176
- try {
177
- $this->usage_tracking->handle_tracking_opt_in();
178
- } catch ( WP_Die_Exception $e ) {
179
- $wp_die_args = $e->get_wp_die_args();
180
- $this->assertEquals( 403, $wp_die_args['args']['response'], 'wp_die called has "Forbidden" status' );
181
- }
182
-
183
- $this->assertFalse( !! $this->usage_tracking->is_tracking_enabled(), 'Usage tracking disabled' );
184
- $this->assertFalse( !! get_option( $this->usage_tracking->get_prefix() . '_usage_tracking_opt_in_hide' ), 'Dialog not hidden' );
185
- }
186
-
187
- /* END test ajax request cases */
188
-
189
- /**
190
- * Ensure that a request is made to the correct URL with the given
191
- * properties and the default properties.
192
- *
193
- * @covers {Prefix}_Usage_Tracking::send_event
194
- */
195
- public function testSendEvent() {
196
- $event = 'my_event';
197
- $properties = array(
198
- 'button_clicked' => 'my_button',
199
- );
200
- $timestamp = '1234';
201
-
202
- // Enable tracking.
203
- $this->usage_tracking->set_tracking_enabled( true );
204
-
205
- // Capture the network request, save the request URL and arguments, and simulate a WP_Error.
206
- $this->track_http_request['request_params'] = null;
207
- $this->track_http_request['request_url'] = null;
208
- add_filter( 'pre_http_request', array( $this, 'trackHttpRequest' ), 10, 3 );
209
-
210
- $this->usage_tracking->send_event( 'my_event', $properties, $timestamp );
211
-
212
- $parsed_url = parse_url( $this->track_http_request['request_url'] );
213
-
214
- $this->assertEquals( 'pixel.wp.com', $parsed_url['host'], 'Host' );
215
- $this->assertEquals( '/t.gif', $parsed_url['path'], 'Path' );
216
-
217
- $query = array();
218
- parse_str( $parsed_url['query'], $query );
219
-
220
- // Older versions (for PHP 5.2) of PHPUnit do not have this method.
221
- if ( method_exists( $this, 'assertArraySubset' ) ) {
222
- $this->assertArraySubset(
223
- array(
224
- 'button_clicked' => 'my_button',
225
- 'admin_email' => 'admin@example.org',
226
- '_ut' => $this->usage_tracking->get_prefix() . ':site_url',
227
- '_ui' => 'http://example.org',
228
- '_ul' => '',
229
- '_en' => $this->usage_tracking->get_prefix() . '_my_event',
230
- '_ts' => '1234000',
231
- '_' => '_',
232
- ), $query, 'Query parameters'
233
- );
234
- }
235
- }
236
-
237
- /**
238
- * Ensure that the request is not made if tracking is not enabled, unless
239
- * $force is true.
240
- *
241
- * @covers {Prefix}_Usage_Tracking::send_event
242
- */
243
- public function testSendEventWithTrackingDisabled() {
244
- $event = 'my_event';
245
- $properties = array(
246
- 'button_clicked' => 'my_button',
247
- );
248
- $timestamp = '1234';
249
-
250
- // Disable tracking.
251
- $this->usage_tracking->set_tracking_enabled( false );
252
-
253
- // Count network requests.
254
- $this->event_counts['http_request'] = 0;
255
- add_filter( 'pre_http_request', array( $this, 'countHttpRequest' ) );
256
-
257
- $this->usage_tracking->send_event( 'my_event', $properties, $timestamp );
258
- $this->assertEquals( 0, $this->event_counts['http_request'], 'No request when disabled' );
259
- }
260
-
261
- /**
262
- * Ensure that the request is only sent when the setting is enabled.
263
- *
264
- * @covers {Prefix}_Usage_Tracking::maybe_send_usage_data
265
- */
266
- public function testSendUsageData() {
267
- // Count the number of network requests.
268
- $this->event_counts['http_request'] = 0;
269
- add_filter( 'pre_http_request', array( $this, 'countHttpRequest' ) );
270
-
271
- // Setting is not set, ensure the request is not sent.
272
- $this->usage_tracking->send_usage_data();
273
- $this->assertEquals( 0, $this->event_counts['http_request'], 'Request not sent when Usage Tracking disabled' );
274
-
275
- // Set the setting and ensure request is sent.
276
- $this->usage_tracking->set_tracking_enabled( true );
277
-
278
- $this->usage_tracking->send_usage_data();
279
- $this->assertEquals( 2, $this->event_counts['http_request'], 'Request sent when Usage Tracking enabled' );
280
- }
281
-
282
- /* Tests for tracking opt in dialog */
283
-
284
- /**
285
- * When setting is not set, dialog is not hidden, and user has capability,
286
- * we should see the dialog and Enable Usage Tracking button.
287
- *
288
- * @covers {Prefix}_Usage_Tracking::_maybe_display_tracking_opt_in
289
- */
290
- public function testDisplayTrackingOptIn() {
291
- $this->setupOptInDialog();
292
-
293
- $this->expectOutputRegex( '/Enable Usage Tracking/' );
294
- $this->usage_tracking->maybe_display_tracking_opt_in();
295
- }
296
-
297
- /**
298
- * When setting is already set, dialog should not appear.
299
- *
300
- * @covers {Prefix}_Usage_Tracking::_maybe_display_tracking_opt_in
301
- */
302
- public function testDoNotDisplayTrackingOptInWhenSettingEnabled() {
303
- $this->setupOptInDialog();
304
- $this->usage_tracking->set_tracking_enabled( true );
305
-
306
- $this->expectOutputString( '' );
307
- $this->usage_tracking->maybe_display_tracking_opt_in();
308
- }
309
-
310
- /**
311
- * When option is set to hide the dialog, it should not appear.
312
- *
313
- * @covers {Prefix}_Usage_Tracking::_maybe_display_tracking_opt_in
314
- */
315
- public function testDoNotDisplayTrackingOptInWhenDialogHidden() {
316
- $this->setupOptInDialog();
317
- update_option( $this->usage_tracking->get_prefix() . '_usage_tracking_opt_in_hide', true );
318
-
319
- $this->expectOutputString( '' );
320
- $this->usage_tracking->maybe_display_tracking_opt_in();
321
- }
322
-
323
- /**
324
- * When user does not have permission to manage usage tracking, dialog
325
- * should not appear.
326
- *
327
- * @covers {Prefix}_Usage_Tracking::_maybe_display_tracking_opt_in
328
- */
329
- public function testDoNotDisplayTrackingOptInWhenUserNotAuthorized() {
330
- $this->setupOptInDialog();
331
- $this->allowCurrentUserToEnableTracking( false );
332
-
333
- $this->expectOutputString( '' );
334
- $this->usage_tracking->maybe_display_tracking_opt_in();
335
- }
336
-
337
- /* END tests for tracking opt in dialog */
338
-
339
- /* Tests for system data */
340
-
341
- /**
342
- * Tests the basic structure for collected system data.
343
- *
344
- * @covers {Prefix}_Usage_Tracking::get_system_data
345
- * @group track-system-data
346
- */
347
- public function testSystemDataStructure() {
348
- global $wp_version;
349
-
350
- $system_data = $this->usage_tracking->get_system_data();
351
-
352
- $this->assertInternalType( 'array', $system_data, 'System data must be returned as an array' );
353
-
354
- $this->assertArrayHasKey( 'wp_version', $system_data, '`wp_version` key must exist in system data' );
355
- $this->assertEquals( $wp_version, $system_data['wp_version'], '`wp_version` does not match expected value' );
356
-
357
- $this->assertArrayHasKey( 'php_version', $system_data, '`php_version` key must exist in system data' );
358
- $this->assertEquals( PHP_VERSION, $system_data['php_version'], '`php_version` does not match expected value' );
359
-
360
- $this->assertArrayHasKey( 'locale', $system_data, '`locale` key must exist in system data' );
361
- $this->assertEquals( get_locale(), $system_data['locale'], '`locale` does not match expected value' );
362
-
363
- $this->assertArrayHasKey( 'multisite', $system_data, '`multisite` key must exist in system data' );
364
- $this->assertEquals( is_multisite(), $system_data['multisite'], '`multisite` does not match expected value' );
365
-
366
- /**
367
- * Current active theme.
368
- *
369
- * @var WP_Theme $theme
370
- */
371
- $theme = wp_get_theme();
372
-
373
- $this->assertArrayHasKey( 'active_theme', $system_data, '`active_theme` key must exist in system data' );
374
- $this->assertEquals( $theme['Name'], $system_data['active_theme'], '`active_theme` does not match expected value' );
375
-
376
- $this->assertArrayHasKey( 'active_theme_version', $system_data, '`active_theme_version` key must exist in system data' );
377
- $this->assertEquals( $theme['Version'], $system_data['active_theme_version'], '`active_theme_version` does not match expected value' );
378
-
379
- $this->assertArrayHasKey( 'plugin_my_favorite_plugin', $system_data, '`plugin_my_favorite_plugin` key must exist in system data' );
380
- $this->assertEquals( '1.0.0', $system_data['plugin_my_favorite_plugin'], '`plugin_my_favorite_plugin` does not match expected value' );
381
-
382
- $this->assertArrayHasKey( 'plugin_hello', $system_data, '`plugin_hello` key must exist in system data' );
383
- $this->assertEquals( '1.0.0', $system_data['plugin_my_favorite_plugin'], '`plugin_hello` does not match expected value' );
384
-
385
- $this->assertArrayHasKey( 'plugin_test', $system_data, '`plugin_test` key must exist in system data' );
386
- $this->assertEquals( '1.0.0', $system_data['plugin_test'], '`plugin_test` does not match expected value' );
387
-
388
- $this->assertArrayNotHasKey( 'plugin_jetpack', $system_data, '`plugin_jetpack` key must NOT exist in system data' );
389
- $this->assertArrayNotHasKey( 'plugin_test_dev', $system_data, '`plugin_test_dev` key must NOT exist in system data' );
390
-
391
- $plugin_prefix_count = 0;
392
- foreach ( $system_data as $key => $value ) {
393
- if ( 1 === preg_match( '/^plugin_/', $key ) ) {
394
- $plugin_prefix_count++;
395
- }
396
- }
397
-
398
- $this->assertEquals( 3, $plugin_prefix_count );
399
- }
400
-
401
- /* END tests for system data */
402
-
403
- /****** Helper methods ******/
404
-
405
- /**
406
- * Helper method for ajax request.
407
- */
408
- private function setupAjaxRequest() {
409
- // Simulate an ajax request.
410
- add_filter( 'wp_doing_ajax', '__return_true' );
411
-
412
- // Set up nonce.
413
- $_REQUEST['nonce'] = wp_create_nonce( 'tracking-opt-in' );
414
-
415
- // Ensure current user can enable tracking.
416
- $this->allowCurrentUserToEnableTracking();
417
-
418
- // When wp_die is called, save the args and throw an exception to stop.
419
- // execution.
420
- add_filter( 'wp_die_ajax_handler', array( $this, 'ajaxDieHandler' ) );
421
- }
422
-
423
- /**
424
- * Helper method to set up tracking opt-in dialog.
425
- */
426
- private function setupOptInDialog() {
427
- // Ensure current user can enable tracking.
428
- $this->allowCurrentUserToEnableTracking();
429
-
430
- // Ensure setting is not set.
431
- $this->usage_tracking->set_tracking_enabled( false );
432
- }
433
-
434
- /**
435
- * Update the capaility for the current user to be able to enable or
436
- * disable tracking.
437
- *
438
- * @param bool $allow true if the current user should be allowed to update.
439
- * the tracking setting, false otherwise. Default: true
440
- **/
441
- private function allowCurrentUserToEnableTracking( $allow = true ) {
442
- $user = wp_get_current_user();
443
-
444
- if ( $allow ) {
445
- $user->add_cap( 'manage_usage_tracking' );
446
- } else {
447
- $user->remove_cap( 'manage_usage_tracking' );
448
- }
449
- }
450
-
451
- /**
452
- * Callback helpers.
453
- */
454
-
455
- /**
456
- * Basic callback for usage data.
457
- *
458
- * @return array
459
- */
460
- public function basicDataCallback() {
461
- return array( 'testing' => true );
462
- }
463
-
464
- /**
465
- * Sets the die handler for ajax request.
466
- *
467
- * @return array
468
- */
469
- public function ajaxDieHandler() {
470
- return array( $this, 'ajaxDieHandlerCallback' );
471
- }
472
-
473
- /**
474
- * Error handler for ajax requests.
475
- *
476
- * @param string $message
477
- * @param string $title
478
- * @param array $args
479
- *
480
- * @throws WP_Die_Exception
481
- */
482
- public function ajaxDieHandlerCallback( $message, $title, $args ) {
483
- $e = new WP_Die_Exception( 'wp_die called' );
484
- $e->set_wp_die_args( $message, $title, $args );
485
- throw $e;
486
- }
487
-
488
- /**
489
- * Count the number of times an event is scheduled.
490
- *
491
- * @param object $event
492
- *
493
- * @return object
494
- */
495
- public function countScheduleEvent( $event ) {
496
- $prefix = $this->usage_tracking->get_prefix();
497
- if ( $event->hook === $prefix . '_usage_tracking_send_usage_data' ) {
498
- $this->event_counts['schedule_event']++;
499
- }
500
- return $event;
501
- }
502
-
503
- /**
504
- * Count the number of HTTP requests.
505
- *
506
- * @return WP_Error
507
- */
508
- public function countHttpRequest() {
509
- $this->event_counts['http_request']++;
510
- return new WP_Error();
511
- }
512
-
513
- /**
514
- * Track HTTP request params and URL.
515
- *
516
- * @param string $preempt
517
- * @param array $r
518
- * @param string $url
519
- *
520
- * @return WP_Error
521
- */
522
- public function trackHttpRequest( $preempt, $r, $url ) {
523
- $this->track_http_request['request_params'] = $r;
524
- $this->track_http_request['request_url'] = $url;
525
- return new WP_Error();
526
- }
527
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/wpjm_rest/class-wp-job-manager-rest-bootstrap.php DELETED
@@ -1,196 +0,0 @@
1
- <?php
2
- /**
3
- * Bootstrap
4
- *
5
- * Loads classes and creates an Environment subclass instance from
6
- * the specified lib location, with the specified prefix
7
- *
8
- * @package Mixtape
9
- */
10
-
11
- if ( ! defined( 'ABSPATH' ) ) {
12
- exit;
13
- }
14
-
15
- /**
16
- * Class WP_Job_Manager_REST_Bootstrap
17
- *
18
- * This is the entry point for.
19
- */
20
- class WP_Job_Manager_REST_Bootstrap {
21
- const MINIMUM_PHP_VERSION = '5.2.0';
22
-
23
- /**
24
- * The Environment we will use
25
- *
26
- * @var null|object the Environment implementation.
27
- */
28
- private $environment = null;
29
-
30
- /**
31
- * The class loader we will use
32
- *
33
- * @var null|WP_Job_Manager_REST_Classloader
34
- */
35
- private $class_loader = null;
36
-
37
- /**
38
- * Construct a new Bootstrap
39
- *
40
- * @param null|WP_Job_Manager_REST_Interfaces_Classloader $class_loader The class loader to use.
41
- */
42
- private function __construct( $class_loader = null ) {
43
- $this->class_loader = $class_loader;
44
- }
45
-
46
- /**
47
- * Check compatibility of PHP Version.
48
- *
49
- * @return bool
50
- */
51
- public static function is_compatible() {
52
- return version_compare( phpversion(), self::MINIMUM_PHP_VERSION, '>=' );
53
- }
54
-
55
- /**
56
- * Get Base Dir
57
- *
58
- * @return string
59
- */
60
- public static function get_base_dir() {
61
- return untrailingslashit( dirname( __FILE__ ) );
62
- }
63
-
64
- /**
65
- * Create a Bootstrap, unless we are using a really early php version (< 5.3.0)
66
- *
67
- * @param WP_Job_Manager_REST_Interfaces_Classloader|null $class_loader The class loader to use.
68
- * @return WP_Job_Manager_REST_Bootstrap|null
69
- */
70
- public static function create( $class_loader = null ) {
71
- if ( empty( $class_loader ) ) {
72
- include_once( 'interfaces/class-wp-job-manager-rest-interfaces-classloader.php' );
73
- include_once( 'class-wp-job-manager-rest-classloader.php' );
74
- $prefix = str_replace( '_Bootstrap', '', __CLASS__ );
75
- $base_dir = self::get_base_dir();
76
- $class_loader = new WP_Job_Manager_REST_Classloader( $prefix, $base_dir );
77
- }
78
- return new self( $class_loader );
79
- }
80
-
81
- /**
82
- * Run the app
83
- *
84
- * @return bool
85
- */
86
- public function run() {
87
- if ( ! self::is_compatible() ) {
88
- return false;
89
- }
90
- $this->load()
91
- ->environment()->start();
92
- return true;
93
- }
94
-
95
- /**
96
- * Optional: Instead of calling load() you can
97
- * register as an auto-loader
98
- *
99
- * @return WP_Job_Manager_REST_Bootstrap $this
100
- */
101
- function register_autoload() {
102
- if ( function_exists( 'spl_autoload_register' ) ) {
103
- spl_autoload_register( array( $this->class_loader(), 'load_class' ), true );
104
- }
105
- return $this;
106
- }
107
-
108
- /**
109
- * Loads all classes
110
- *
111
- * @return WP_Job_Manager_REST_Bootstrap $this
112
- * @throws Exception In case a class/file is not found.
113
- */
114
- function load() {
115
- $this->class_loader()
116
- ->load_class( 'Interfaces_Data_Store' )
117
- ->load_class( 'Interfaces_Registrable' )
118
- ->load_class( 'Interfaces_Type' )
119
- ->load_class( 'Interfaces_Model' )
120
- ->load_class( 'Interfaces_Builder' )
121
- ->load_class( 'Interfaces_Model_Collection' )
122
- ->load_class( 'Interfaces_Controller' )
123
- ->load_class( 'Interfaces_Controller_Bundle' )
124
- ->load_class( 'Interfaces_Permissions_Provider' )
125
- ->load_class( 'Exception' )
126
- ->load_class( 'Expect' )
127
- ->load_class( 'Events' )
128
- ->load_class( 'Environment' )
129
- ->load_class( 'Type' )
130
- ->load_class( 'Type_String' )
131
- ->load_class( 'Type_Integer' )
132
- ->load_class( 'Type_Number' )
133
- ->load_class( 'Type_Boolean' )
134
- ->load_class( 'Type_Array' )
135
- ->load_class( 'Type_TypedArray' )
136
- ->load_class( 'Type_Nullable' )
137
- ->load_class( 'Type_Model' )
138
- ->load_class( 'Type_Registry' )
139
- ->load_class( 'Data_Store_Nil' )
140
- ->load_class( 'Data_Store_Abstract' )
141
- ->load_class( 'Data_Store_CustomPostType' )
142
- ->load_class( 'Data_Store_Option' )
143
- ->load_class( 'Permissions_Any' )
144
- ->load_class( 'Field_Declaration' )
145
- ->load_class( 'Field_Declaration_Builder' )
146
- ->load_class( 'Model' )
147
- ->load_class( 'Model_Settings' )
148
- ->load_class( 'Model_Collection' )
149
- ->load_class( 'Controller' )
150
- ->load_class( 'Controller_Action' )
151
- ->load_class( 'Controller_Model' )
152
- ->load_class( 'Controller_Settings' )
153
- ->load_class( 'Controller_Route' )
154
- ->load_class( 'Controller_CRUD' )
155
- ->load_class( 'Controller_Bundle' )
156
- ->load_class( 'Controller_Extension' )
157
- ->load_class( 'Controller_Bundle_Builder' );
158
-
159
- return $this;
160
- }
161
-
162
- /**
163
- * Load Unit Testing Base Classes
164
- *
165
- * @return WP_Job_Manager_REST_Bootstrap $this
166
- */
167
- function load_testing_classes() {
168
- $this->class_loader()
169
- ->load_class( 'Testing_TestCase' )
170
- ->load_class( 'Testing_Model_TestCase' )
171
- ->load_class( 'Testing_Controller_TestCase' );
172
- return $this;
173
- }
174
-
175
- /**
176
- * Get the class loader
177
- *
178
- * @return WP_Job_Manager_REST_Classloader
179
- */
180
- function class_loader() {
181
- return $this->class_loader;
182
- }
183
-
184
- /**
185
- * Lazy-load the environment
186
- *
187
- * @return WP_Job_Manager_REST_Environment
188
- */
189
- public function environment() {
190
- if ( null === $this->environment ) {
191
- $this->load();
192
- $this->environment = new WP_Job_Manager_REST_Environment( $this );
193
- }
194
- return $this->environment;
195
- }
196
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/wpjm_rest/class-wp-job-manager-rest-classloader.php DELETED
@@ -1,145 +0,0 @@
1
- <?php
2
- /**
3
- * Default Mixtape_Interfaces_Class_Loader Implementation
4
- *
5
- * Loads classes in nested folders.
6
- *
7
- * @package Mixtape
8
- */
9
-
10
- if ( ! defined( 'ABSPATH' ) ) {
11
- exit;
12
- }
13
-
14
- /**
15
- * Class WP_Job_Manager_REST_Class_Loader
16
- */
17
- class WP_Job_Manager_REST_Classloader implements WP_Job_Manager_REST_Interfaces_Classloader {
18
- /**
19
- * Keep a record of classes loaded
20
- *
21
- * @var array The loaded class map.
22
- */
23
- private $loaded_classes;
24
- /**
25
- * The prefix to use (e.g. Mixtape)
26
- *
27
- * @var string
28
- */
29
- private $prefix;
30
- /**
31
- * The directory the loader looks for classes.
32
- *
33
- * @var string
34
- */
35
- private $base_dir;
36
-
37
- /**
38
- * WP_Job_Manager_REST_Class_Loader constructor.
39
- *
40
- * @param string $prefix The prefix to use (e.g. Mixtape).
41
- * @param string $base_dir The directory the loader looks for classes.
42
- *
43
- * @throws Exception Throws if an invalid directory is provided.
44
- */
45
- public function __construct( $prefix, $base_dir ) {
46
- $this->loaded_classes = array();
47
- $this->prefix = $prefix;
48
- $this->base_dir = $base_dir;
49
- if ( ! is_dir( $this->base_dir ) ) {
50
- throw new Exception( 'base_dir does not exist: ' . $this->base_dir );
51
- }
52
- }
53
-
54
- /**
55
- * Loads a class
56
- *
57
- * @param string $class_name The class to load.
58
- *
59
- * @return WP_Job_Manager_REST_Interfaces_Classloader
60
- * @throws Exception Throws in case include_class_file fails.
61
- */
62
- public function load_class( $class_name ) {
63
- $path = $this->get_path_to_class_file( $class_name );
64
- return $this->include_class_file( $path );
65
- }
66
-
67
- /**
68
- * Get path_to_class_file
69
- *
70
- * @param string $class_name The class.
71
- *
72
- * @return string The full path to the file.
73
- */
74
- public function get_path_to_class_file( $class_name ) {
75
- return path_join( $this->base_dir, $this->class_name_to_relative_path( $class_name ) );
76
- }
77
-
78
- /**
79
- * Class_name_to_relative_path
80
- *
81
- * @param string $class_name The class name.
82
- * @param null|string $prefix The prefix.
83
- *
84
- * @return string
85
- */
86
- public function class_name_to_relative_path( $class_name, $prefix = null ) {
87
- $lowercase = strtolower( $this->prefixed_class_name( $class_name, $prefix ) );
88
- $file_name = 'class-' . str_replace( '_', '-', $lowercase ) . '.php';
89
- $parts = explode( '_', strtolower( $this->strip_prefix( $class_name, $prefix ) ) );
90
- array_pop( $parts );
91
- $parts[] = $file_name;
92
- return implode( DIRECTORY_SEPARATOR, $parts );
93
- }
94
-
95
- /**
96
- * Prefixed_class_name
97
- *
98
- * @param string $class_name The class name.
99
- * @param null|string $prefix The prefix.
100
- *
101
- * @return string
102
- */
103
- public function prefixed_class_name( $class_name, $prefix = null ) {
104
- if ( empty( $prefix ) ) {
105
- $prefix = $this->prefix;
106
- }
107
- return $prefix . '_' . $this->strip_prefix( $class_name, $prefix );
108
- }
109
-
110
- /**
111
- * Strip_prefix
112
- *
113
- * @param string $class_name The class name.
114
- * @param null|string $prefix The prefix.
115
- *
116
- * @return string
117
- */
118
- private function strip_prefix( $class_name, $prefix = null ) {
119
- if ( empty( $prefix ) ) {
120
- $prefix = $this->prefix;
121
- }
122
- return str_replace( $prefix, '', $class_name );
123
- }
124
-
125
- /**
126
- * Include_class_file
127
- *
128
- * @param string $path_to_the_class The file path.
129
- *
130
- * @return string
131
- * @throws Exception Throws when the file does not exist.
132
- */
133
- private function include_class_file( $path_to_the_class ) {
134
- if ( isset( $this->loaded_classes[ $path_to_the_class ] ) ) {
135
- return $this;
136
- }
137
- if ( ! file_exists( $path_to_the_class ) ) {
138
- throw new Exception( $path_to_the_class . ' not found' );
139
- }
140
- $included = include_once( $path_to_the_class );
141
- $this->loaded_classes[ $path_to_the_class ] = $included;
142
-
143
- return $this;
144
- }
145
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/wpjm_rest/class-wp-job-manager-rest-controller.php DELETED
@@ -1,285 +0,0 @@
1
- <?php
2
- /**
3
- * Controller
4
- *
5
- * @package Mixtape/Controller
6
- */
7
-
8
- if ( ! defined( 'ABSPATH' ) ) {
9
- exit;
10
- }
11
-
12
- /**
13
- * Class WP_Job_Manager_REST_Controller
14
- */
15
- class WP_Job_Manager_REST_Controller extends WP_REST_Controller implements WP_Job_Manager_REST_Interfaces_Controller {
16
- const HTTP_CREATED = 201;
17
- const HTTP_OK = 200;
18
- const HTTP_BAD_REQUEST = 400;
19
- const HTTP_NOT_FOUND = 404;
20
-
21
- /**
22
- * The bundle this belongs to.
23
- *
24
- * @var WP_Job_Manager_REST_Controller_Bundle
25
- */
26
- protected $controller_bundle;
27
-
28
- /**
29
- * The endpoint base (e.g. /users). Override in subclasses.
30
- *
31
- * @var string
32
- */
33
- protected $base = null;
34
- /**
35
- * Our Handlers
36
- *
37
- * @var array
38
- */
39
- private $routes = array();
40
-
41
- /**
42
- * Optional, an enviromnent.
43
- *
44
- * @var null|WP_Job_Manager_REST_Environment
45
- */
46
- protected $environment = null;
47
-
48
- /**
49
- * WP_Job_Manager_REST_Rest_Api_Controller constructor.
50
- */
51
- public function __construct() {
52
- }
53
-
54
- /**
55
- * Set Controller Bundle
56
- *
57
- * @param WP_Job_Manager_REST_Controller_Bundle $controller_bundle Controller Bundle this belongs to.
58
- *
59
- * @return WP_Job_Manager_REST_Controller $this
60
- */
61
- public function set_controller_bundle( $controller_bundle ) {
62
- $this->controller_bundle = $controller_bundle;
63
- return $this;
64
- }
65
-
66
- /**
67
- * Set the Environment for this Controller.
68
- *
69
- * @param WP_Job_Manager_REST_Environment|null $environment The Environment.
70
- * @return WP_Job_Manager_REST_Controller
71
- */
72
- public function set_environment( $environment ) {
73
- $this->environment = $environment;
74
- return $this;
75
- }
76
-
77
- /**
78
- * Register This Controller
79
- *
80
- * @param WP_Job_Manager_REST_Controller_Bundle $bundle The bundle to register with.
81
- * @param WP_Job_Manager_REST_Environment $environment The Environment to use.
82
- * @throws WP_Job_Manager_REST_Exception Throws.
83
- *
84
- * @return bool|WP_Error true if valid otherwise error.
85
- */
86
- public function register( $bundle, $environment ) {
87
- $this->set_controller_bundle( $bundle );
88
- $this->set_environment( $environment );
89
- $this->setup();
90
- WP_Job_Manager_REST_Expect::that( ! empty( $this->base ), 'Need to put a string with a backslash in $base' );
91
- $prefix = $this->controller_bundle->get_prefix();
92
- foreach ( $this->routes as $pattern => $route ) {
93
- /**
94
- * The route we want to register.
95
- *
96
- * @var WP_Job_Manager_REST_Controller_Route $route
97
- */
98
- $params = $route->as_array();
99
- $result = register_rest_route( $prefix, $this->base . $params['pattern'], $params['actions'] );
100
- if ( false === $result ) {
101
- // For now we throw on error, maybe we just need to warn though.
102
- throw new WP_Job_Manager_REST_Exception( 'Registration failed' );
103
- }
104
- }
105
-
106
- return true;
107
- }
108
-
109
- /**
110
- * Create Action
111
- *
112
- * @param string $action_name Action Name.
113
- * @param null|string|array|callable $callback Callback.
114
- * @return WP_Job_Manager_REST_Controller_Action
115
- */
116
- public function action( $action_name, $callback = null ) {
117
- $route_action = new WP_Job_Manager_REST_Controller_Action( $this, $action_name );
118
- if ( null !== $callback ) {
119
- $route_action->callback( $callback );
120
- }
121
-
122
- return $route_action;
123
- }
124
-
125
- /**
126
- * Do any additional Configuration. Runs inside register before any register_rest_route
127
- *
128
- * This is a good place for overriding classes to define routes and handlers
129
- */
130
- protected function setup() {
131
- }
132
-
133
- /**
134
- * Succeed
135
- *
136
- * @param array $data The dto.
137
- *
138
- * @return WP_REST_Response
139
- */
140
- public function ok( $data ) {
141
- return $this->respond( $data, self::HTTP_OK );
142
- }
143
-
144
- /**
145
- * Created
146
- *
147
- * @param array $data The dto.
148
- *
149
- * @return WP_REST_Response
150
- */
151
- public function created( $data ) {
152
- return $this->respond( $data, self::HTTP_CREATED );
153
- }
154
-
155
- /**
156
- * Bad request
157
- *
158
- * @param array|WP_Error $data The dto.
159
- *
160
- * @return WP_REST_Response
161
- */
162
- public function bad_request( $data ) {
163
- return $this->respond( $data, self::HTTP_BAD_REQUEST );
164
- }
165
-
166
- /**
167
- * Not Found (404)
168
- *
169
- * @param string $message The message.
170
- *
171
- * @return WP_REST_Response
172
- */
173
- public function not_found( $message ) {
174
- return $this->respond( array(
175
- 'message' => $message,
176
- ), self::HTTP_NOT_FOUND );
177
- }
178
-
179
- /**
180
- * Respond
181
- *
182
- * @param array|WP_REST_Response|WP_Error|mixed $data The thing.
183
- * @param int $status The Status.
184
- *
185
- * @return mixed|WP_REST_Response
186
- */
187
- public function respond( $data, $status ) {
188
- if ( is_a( $data, 'WP_REST_Response' ) ) {
189
- return $data;
190
- }
191
-
192
- return new WP_REST_Response( $data, $status );
193
- }
194
-
195
- /**
196
- * Permissions for get_items
197
- *
198
- * @param WP_REST_Request $request Request.
199
- * @return bool
200
- */
201
- public function index_permissions_check( $request ) {
202
- return $this->permissions_check( $request, 'index' );
203
- }
204
-
205
- /**
206
- * Permissions for get_item
207
- *
208
- * @param WP_REST_Request $request The request.
209
- * @return bool
210
- */
211
- public function show_permissions_check( $request ) {
212
- return $this->permissions_check( $request, 'show' );
213
- }
214
-
215
- /**
216
- * Permissions for create_item
217
- *
218
- * @param WP_REST_Request $request Request.
219
- * @return bool
220
- */
221
- public function create_permissions_check( $request ) {
222
- return $this->permissions_check( $request, 'create' );
223
- }
224
-
225
- /**
226
- * Permissions for update_item
227
- *
228
- * @param WP_REST_Request $request Request.
229
- * @return bool
230
- */
231
- public function update_permissions_check( $request ) {
232
- return $this->permissions_check( $request, 'update' );
233
- }
234
-
235
- /**
236
- * Permissions for delete
237
- *
238
- * @param WP_REST_Request $request Request.
239
- * @return bool
240
- */
241
- public function delete_permissions_check( $request ) {
242
- return $this->permissions_check( $request, 'delete' );
243
- }
244
-
245
- /**
246
- * Generic Permissions Check.
247
- *
248
- * @param WP_REST_Request $request Request.
249
- * @param string $action One of (index, show, create, update, delete, any).
250
- * @return bool
251
- */
252
- function permissions_check( $request, $action = 'any' ) {
253
- return true;
254
- }
255
-
256
- /**
257
- * Add a route
258
- *
259
- * @param string $pattern The route pattern (e.g. '/').
260
- * @return WP_Job_Manager_REST_Controller_Route
261
- */
262
- function add_route( $pattern = '' ) {
263
- $route = new WP_Job_Manager_REST_Controller_Route( $this, $pattern );
264
- $this->routes[ $pattern ] = $route;
265
- return $this->routes[ $pattern ];
266
- }
267
-
268
- /**
269
- * Get Environment
270
- *
271
- * @return WP_Job_Manager_REST_Environment
272
- */
273
- protected function environment() {
274
- return $this->environment;
275
- }
276
-
277
- /**
278
- * Get base url
279
- *
280
- * @return string
281
- */
282
- function get_base() {
283
- return rest_url( $this->controller_bundle->get_prefix() . $this->base );
284
- }
285
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
lib/wpjm_rest/class-wp-job-manager-rest-environment.php DELETED
@@ -1,426 +0,0 @@
1
- <?php
2
- /**
3
- * Environment
4
- *
5
- * Contains variables, rest api, type and model definitions
6
- *
7
- * @package Mixtape
8
- */
9
-
10
- if ( ! defined( 'ABSPATH' ) ) {
11
- exit;
12
- }
13
-
14
- /**
15
- * Class WP_Job_Manager_REST_Environment
16
- *
17
- * Our global Environment
18
- *
19
- * @package Mixtape
20
- */
21
- class WP_Job_Manager_REST_Environment {
22
- const REGISTRABLE = 'IRegistrable';
23
- const BUNDLES = 'Bundles';
24
- const MODELS = 'Models';
25
-
26
- /**
27
- * This environment's registered REST bundles
28
- *
29
- * (used for versioned APIs, or logical grouping of related api parts)
30
- *
31
- * @var array
32
- */
33
- protected $rest_apis;
34
-
35
- /**
36
- * This environment's model definitions
37
- *
38
- * @var array
39
- */
40
- protected $model_definitions;
41
-
42
- /**
43
- * The Environment Variables
44
- *
45
- * A key-value array of things.
46
- *
47
- * @var array
48
- */
49
- protected $variables;
50
-
51
- /**
52
- * Did this Environment start?
53
- *
54
- * @var bool
55
- */
56
- private $has_started;
57
-
58
- /**
59
- * Our Bootstrap
60
- *
61
- * @var WP_Job_Manager_REST_Bootstrap
62
- */
63
- private $bootstrap;
64
-
65
- /**
66
- * Our Type Registry
67
- *
68
- * @var WP_Job_Manager_REST_Type_Registry
69
- */
70
- private $type_registry;
71
-
72
- /**
73
- * Events
74
- *
75
- * @var WP_Job_Manager_REST_Events
76
- */
77
- private $event_dispatcher;
78
-
79
- /**
80
- * Mixtape_Environment constructor.
81
- *
82
- * @param WP_Job_Manager_REST_Bootstrap $bootstrap The bootstrap.
83
- */
84
- public function __construct( $bootstrap ) {
85
- $this->event_dispatcher = new WP_Job_Manager_REST_Events();
86
- $this->bootstrap = $bootstrap;
87
- $this->has_started = false;
88
- $this->rest_apis = array();
89
- $this->variables = array();
90
- $this->model_definitions = array();
91
- $this->type_registry = new WP_Job_Manager_REST_Type_Registry();
92
- $this->type_registry->initialize( $this );
93
- // initialize our array vars.
94
- $this->array_var( self::MODELS )
95
- ->array_var( self::REGISTRABLE )
96
- ->array_var( self::BUNDLES );
97
- }
98
-
99
- /**
100
- * Get Event Dispacher
101
- *
102
- * @return WP_Job_Manager_REST_Events
103
- */
104
- public function get_event_dispatcher() {
105
- return $this->event_dispatcher;
106
- }
107
-
108
- /**
109
- * Push a Builder to the Environment.
110
- *
111
- * All builders are evaluated lazily when needed
112
- *
113
- * @param string $where The queue to push the builder to.
114
- * @param WP_Job_Manager_REST_Interfaces_Builder $builder The builder to push.
115
- *
116
- * @return WP_Job_Manager_REST_Environment $this
117
- * @throws WP_Job_Manager_REST_Exception In case the $builder is not a Mixtape_Interfaces_Builder.
118
- */
119
- public function push_builder( $where, $builder ) {
120
- WP_Job_Manager_REST_Expect::that( is_string( $where ), '$where should be a string' );
121
- WP_Job_Manager_REST_Expect::is_a( $builder, 'WP_Job_Manager_REST_Interfaces_Builder' );
122
- return $this->array_var( $where, $builder );
123
- }
124
-
125
- /**
126
- * Retrieve a previously defined WP_Job_Manager_REST_Model
127
- *
128
- * @param string $class the class name.
129
- * @return WP_Job_Manager_REST_Model the definition.
130
- * @throws WP_Job_Manager_REST_Exception Throws in case the model is not registered.
131
- */
132
- public function model( $class ) {
133
- if ( ! class_exists( $class ) ) {
134
- throw new WP_Job_Manager_REST_Exception( $class . ' does not exist' );
135
- }
136
- WP_Job_Manager_REST_Expect::that( isset( $this->model_definitions[ $class ] ), $class . ' definition does not exist' );
137
- return $this->model_definitions[ $class ];
138
- }
139
-
140
- /**
141
- * Time to build pending models and bundles
142
- *
143
- * @param string $type One of (models, bundles).
144
- * @return WP_Job_Manager_REST_Environment
145
- */
146
- private function load_pending_builders( $type ) {
147
- $things = $this->get( $type );
148
- if ( ! empty( $things ) && is_array( $things ) ) {
149
- foreach ( $things as $pending ) {
150
- /**
151
- * Our pending builder.
152
- *
153
- * @var WP_Job_Manager_REST_Interfaces_Builder $pending Our builder.
154
- */
155
- if ( self::BUNDLES === $type ) {
156
- $this->add_rest_bundle( $pending->build() );
157
- }
158
- }
159
- }
160
-
161
- return $this;
162
- }
163
-
164
- /**
165
- * Start things up
166
- *
167
- * This should be called once our Environment is set up to our liking.
168
- * Evaluates all Builders, creating missing REST Api and Model Definitions.
169
- *
170
- *
171
- * It is recommended we hook this into 'rest_api_init'.
172
- *
173
- * @return WP_Job_Manager_REST_Environment $this
174
- */
175
- public function start() {
176
- if ( ! $this->bootstrap->is_compatible() ) {
177
- // Do not even start on an incompatible system.
178
- return $this;
179
- }
180
-
181
- if ( false === $this->has_started ) {
182
- $this->get_event_dispatcher()->do_action( 'environment_before_start', $this, get_class( $this ) );
183
- $this->load_pending_builders( self::MODELS );
184
- $this->load_pending_builders( self::BUNDLES );
185
- $registrables = $this->get( self::REGISTRABLE ) ? $this->get( self::REGISTRABLE ) : array();
186
- foreach ( $registrables as $registrable ) {
187
- /**
188
- * A Registrable
189
- *
190
- * @var WP_Job_Manager_REST_Interfaces_Registrable $registrable
191
- */
192
- $registrable->register( $this );
193
- }
194
-
195
- /**
196
- * Use this hook to add/remove rest api bundles
197
- *
198
- * @param array $rest_apis The existing rest apis.
199
- * @param WP_Job_Manager_REST_Environment $this The Environment.
200
- */
201
- $rest_apis = (array) $this->get_event_dispatcher()->apply_filters( 'environment_get_rest_apis', $this->rest_apis, $this );
202
-
203
- foreach ( $rest_apis as $k => $bundle ) {
204
- /**
205
- * Register this bundle
206
- *
207
- * @var WP_Job_Manager_REST_Interfaces_Controller_Bundle
208
- */
209
- $bundle->register( $this );
210
- }
211
- $this->has_started = true;
212
- $this->get_event_dispatcher()->do_action( 'environment_after_start', $this );
213
- }
214
-
215
- return $this;
216
- }
217
-
218
- /**
219
- * Add Registrable
220
- *
221
- * @param WP_Job_Manager_REST_Interfaces_Registrable $registrable_thing Registrable.
222
- * @return WP_Job_Manager_REST_Environment
223
- * @throws WP_Job_Manager_REST_Exception When not a WP_Job_Manager_REST_Interfaces_Registrable.
224
- */
225
- public function add_registrable( $registrable_thing ) {
226
- WP_Job_Manager_REST_Expect::is_a( $registrable_thing, 'WP_Job_Manager_REST_Interfaces_Registrable' );
227
- $this->array_var( self::REGISTRABLE, $registrable_thing );
228
- return $this->define_var( get_class( $registrable_thing ), $registrable_thing );
229
- }
230
-
231
- /**
232
- * Has Variable
233
- *
234
- * @param string $name Is this variable Set.
235
- * @return bool
236
- */
237
- public function has_variable( $name ) {
238
- return isset( $this->variables[ $name ] );
239
- }
240
-
241
- /**
242
- * Append to an array
243
- *
244
- * @param string $name The VarArray Name.
245
- * @param mixed $thing The thing.
246
- * @return WP_Job_Manager_REST_Environment
247
- */
248
- public function array_var( $name, $thing = null ) {
249
- return $this->define_var( $name, $thing, true );
250
- }
251
-
252
- /**
253
- * Get A Variable
254
- *
255
- * @param string $name The Variable Name.
256
- * @return mixed|null The variable or null
257
- *
258
- * @throws WP_Job_Manager_REST_Exception Name should be a string.
259
- */
260
- public function get( $name ) {
261
- WP_Job_Manager_REST_Expect::that( is_string( $name ), '$name should be a string' );
262
- $value = $this->has_variable( $name ) ? $this->variables[ $name ] : null;
263
- /**
264
- * Filter the variable value
265
- *
266
- * @param mixed $value The value.
267
- * @param WP_Job_Manager_REST_Environment $this The Environemnt.
268
- * @param string $name The var name.
269
- *
270
- * @return mixed
271
- */
272
- return $this->get_event_dispatcher()->apply_filters( 'variable_get', $value, $this, $name );
273
- }
274
-
275
- /**
276
- * Def.
277
- *
278
- * @param string $name The Variable To Add.
279
- * @param mixed $thing The thing that is associated with the var.
280
- * @param bool $append If true, this var is a list.
281
- *
282
- * @return $this
283
- *
284
- * @throws WP_Job_Manager_REST_Exception When name is not a string.
285
- */
286
- public function define_var( $name, $thing = null, $append = false ) {
287
- WP_Job_Manager_REST_Expect::that( is_string( $name ), '$name should be a string' );
288
- if ( $append && ! $this->has_variable( $name ) ) {
289
- $this->variables[ $name ] = array();
290
- }
291
- if ( null !== $thing ) {
292
- if ( $append ) {
293
- $this->variables[ $name ][] = $thing;
294
- } else {
295
- $this->variables[ $name ] = $thing;
296
- }
297
- }
298
- return $this;
299
- }
300
-
301
- /**
302
- * Auto start on rest_api_init. For more control, use ::start();
303
- */
304
- public function auto_start() {
305
- add_action( 'rest_api_init', array( $this, 'start' ) );
306
- }
307
-
308
- /**
309
- * Get this Environment's bootstrap instance
310
- *
311
- * @return WP_Job_Manager_REST_Bootstrap our bootstrap.
312
- */
313
- public function get_bootstrap() {
314
- return $this->bootstrap;
315
- }
316
-
317
- /**
318
- * Create a new Field Declaration Builder
319
- *
320
- * @param null|string $name Optional, the field name.
321
- * @param null|string $description Optional, the description.
322
- * @param null|string $field_kind The field kind (default 'field').
323
- *
324
- * @return WP_Job_Manager_REST_Field_Declaration_Builder
325
- */
326
- public function field( $name = null, $description = null, $field_kind = null ) {
327
- $builder = new WP_Job_Manager_REST_Field_Declaration_Builder();
328
-
329
- if ( ! empty( $name ) ) {
330
- $builder->with_name( $name );
331
- }
332
-
333
- if ( ! empty( $description ) ) {
334
- $builder->with_description( $description );
335
- }
336
-
337
- if ( empty( $field_kind ) ) {
338
- $field_kind = WP_Job_Manager_REST_Field_Declaration::FIELD;
339
- }
340
-
341
- $builder->with_kind( $field_kind );
342
-
343
- return $builder;
344
- }
345
-
346
- /**
347
- * Get our registered types
348
- *
349
- * @return WP_Job_Manager_REST_Type_Registry
350
- */
351
- public function get_type_registry() {
352
- return $this->type_registry;
353
- }
354
-
355
- /**
356
- * Get a known type definition
357
- *
358
- * @param string $type_name The type name.
359
- * @return WP_Job_Manager_REST_Interfaces_Type
360
- *
361
- * @throws WP_Job_Manager_REST_Exception When provided with an unknown/invalid type.
362
- */
363
- public function type( $type_name ) {
364
- return $this->get_type_registry()->definition( $type_name );
365
- }
366
-
367
- /**
368
- * Define a new REST API Bundle.
369
- *
370
- * @param null|string|WP_Job_Manager_REST_Interfaces_Controller_Bundle $maybe_bundle_or_prefix The bundle name.
371
- * @return WP_Job_Manager_REST_Controller_Bundle_Builder
372
- */
373
- public function rest_api( $maybe_bundle_or_prefix = null ) {
374
- if ( is_a( $maybe_bundle_or_prefix, 'WP_Job_Manager_REST_Interfaces_Controller_Bundle' ) ) {
375
- $builder = new WP_Job_Manager_REST_Controller_Bundle_Builder( $maybe_bundle_or_prefix );
376
- } else {
377
- $builder = new WP_Job_Manager_REST_Controller_Bundle_Builder();
378
- if ( is_string( $maybe_bundle_or_prefix ) ) {
379
- $builder->with_prefix( $maybe_bundle_or_prefix );
380
- }
381
- $builder->with_environment( $this );
382
- }
383
-
384
- $this->push_builder( self::BUNDLES, $builder );
385
- return $builder;
386
- }
387
-
388
- /**
389
- * Define a new Model
390
- *
391
- * @param string $declaration A Model class string.
392
- *
393
- * @return WP_Job_Manager_REST_Model
394
- */
395
- function define_model( $declaration ) {
396
- WP_Job_Manager_REST_Expect::that( class_exists( $declaration ), '$declaration string should be an existing class' );
397
- WP_Job_Manager_REST_Expect::that( in_array( 'WP_Job_Manager_REST_Interfaces_Model', class_implements( $declaration ), true ), '$declaration does not implement WP_Job_Manager_REST_Interfaces_Model' );
398
-
399
- /**
400
- * Create an empty Model to act as our factory (I know this is weird, see php5.2)
401
- *
402
- * @var WP_Job_Manager_REST_Model $factory
403
- */
404
- $factory = new $declaration();
405
- $factory->with_environment( $this );
406
- $factory->with_data_store( new WP_Job_Manager_REST_Data_Store_Nil() );
407
- $factory->with_permissions_provider( new WP_Job_Manager_REST_Permissions_Any() );
408
- $this->model_definitions[ $declaration ] = $factory;
409
- return $factory;
410
- }
411
-
412
- /**
413
- * Add a Bundle to our bundles (muse be Mixtape_Interfaces_Rest_Api_Controller_Bundle)
414
- *
415
- * @param WP_Job_Manager_REST_Interfaces_Controller_Bundle $bundle the bundle.
416
- *
417
- * @return WP_Job_Manager_REST_Environment $this
418
- * @throws WP_Job_Manager_REST_Exception In case it's not a WP_Job_Manager_REST_Interfaces_Controller_Bundle.
419
- */
420
- private function add_rest_bundle(