├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .git-blame-ignore-revs ├── .github ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── -----security-issue.md │ ├── Bug_report.md │ └── Enhancement.md ├── PULL_REQUEST_TEMPLATE.md ├── RELEASE-CHECKLIST.yml ├── git-flow.png └── workflows │ ├── build.yml │ ├── close-issue-on-merge.yml │ ├── create-release.yml │ ├── gardening.yml │ ├── github-pages.yml │ ├── php.yml │ ├── release-checklist.yml │ └── report-build.yml ├── .gitignore ├── .husky └── pre-commit ├── .jshintrc ├── .prettierrc ├── .psalm ├── psalm-baseline.xml └── psalm-loader.php ├── LICENSE ├── assets ├── css │ ├── admin-landing.scss │ ├── admin-notices.scss │ ├── admin.scss │ ├── frontend.scss │ ├── icons.scss │ ├── job-dashboard.scss │ ├── job-listings.scss │ ├── job-overlay.scss │ ├── job-submission.scss │ ├── menu.scss │ ├── mixins.scss │ ├── setup.scss │ ├── ui.dialog.scss │ ├── ui.elements.scss │ ├── ui.form.scss │ ├── ui.neutral.scss │ ├── ui.notice.scss │ ├── ui.scss │ └── wpjm-brand.scss ├── font │ ├── config.json │ ├── job-manager-codes.css │ ├── job-manager.svg │ └── job-manager.woff2 ├── images │ ├── ajax-loader.gif │ ├── company.png │ ├── icons │ │ └── checkmark-icon.svg │ ├── jm-full-logo.png │ └── wpjm-logo.png ├── js │ ├── admin.js │ ├── admin │ │ ├── editor-lifecycle.js │ │ ├── job-editor.js │ │ ├── job-tags-upsell.js │ │ ├── promote-job-modals.js │ │ ├── wpjm-modal.js │ │ └── wpjm-notice-dismiss.js │ ├── ajax-file-upload.js │ ├── ajax-filters.js │ ├── datepicker.js │ ├── job-application.js │ ├── job-dashboard.js │ ├── job-submission.js │ ├── multiselect.js │ ├── stats │ │ ├── impressions.js │ │ ├── observers.js │ │ ├── unique.js │ │ └── utils.js │ ├── term-multiselect.js │ ├── tests │ │ └── empty.js │ ├── ui-theme-support.js │ ├── ui.color-utils.js │ └── wpjm-stats.js └── lib │ ├── jquery-chosen │ ├── chosen.css │ ├── chosen.jquery.js │ ├── chosen.jquery.min.js │ └── images │ │ ├── chosen-sprite.png │ │ └── chosen-sprite@2x.png │ ├── jquery-deserialize │ └── jquery.deserialize.js │ ├── jquery-fileupload │ ├── jquery.fileupload.js │ └── jquery.iframe-transport.js │ ├── jquery-tiptip │ ├── jquery.tipTip.js │ └── jquery.tipTip.min.js │ └── select2 │ ├── select2.full.min.js │ └── select2.min.css ├── changelog.txt ├── composer.json ├── composer.lock ├── docs ├── CONTRIBUTING.md └── api │ ├── README.md │ └── internal.json ├── includes ├── 3rd-party │ ├── 3rd-party.php │ ├── all-in-one-seo-pack.php │ ├── jetpack.php │ ├── polylang.php │ ├── rp4wp.php │ ├── wp-all-import.php │ ├── wpcom.php │ ├── wpml.php │ └── yoast.php ├── abstracts │ ├── abstract-wp-job-manager-email-template.php │ ├── abstract-wp-job-manager-email.php │ └── abstract-wp-job-manager-form.php ├── admin │ ├── class-notices-conditions-checker.php │ ├── class-release-notice.php │ ├── class-wp-job-manager-addons-landing-page.php │ ├── class-wp-job-manager-addons.php │ ├── class-wp-job-manager-admin-notices.php │ ├── class-wp-job-manager-admin.php │ ├── class-wp-job-manager-cpt.php │ ├── class-wp-job-manager-permalink-settings.php │ ├── class-wp-job-manager-promoted-jobs-admin.php │ ├── class-wp-job-manager-settings.php │ ├── class-wp-job-manager-setup.php │ ├── class-wp-job-manager-taxonomy-meta.php │ ├── class-wp-job-manager-writepanels.php │ └── views │ │ ├── html-admin-notice-core-setup.php │ │ ├── html-admin-page-addons.php │ │ ├── html-admin-setup-footer.php │ │ ├── html-admin-setup-header.php │ │ ├── html-admin-setup-opt-in-usage-tracking.php │ │ ├── html-admin-setup-step-1.php │ │ ├── html-admin-setup-step-2.php │ │ └── html-admin-setup-step-3.php ├── class-access-token.php ├── class-dev-tools.php ├── class-guest-session.php ├── class-guest-user.php ├── class-job-dashboard-shortcode.php ├── class-job-listing-stats.php ├── class-job-overlay.php ├── class-stats-dashboard.php ├── class-stats-script.php ├── class-stats.php ├── class-wp-job-manager-ajax.php ├── class-wp-job-manager-api.php ├── class-wp-job-manager-blocks.php ├── class-wp-job-manager-cache-helper.php ├── class-wp-job-manager-category-walker.php ├── class-wp-job-manager-com-api.php ├── class-wp-job-manager-data-cleaner.php ├── class-wp-job-manager-data-exporter.php ├── class-wp-job-manager-dependency-checker.php ├── class-wp-job-manager-email-notifications.php ├── class-wp-job-manager-forms.php ├── class-wp-job-manager-geocode.php ├── class-wp-job-manager-install.php ├── class-wp-job-manager-post-types.php ├── class-wp-job-manager-recaptcha.php ├── class-wp-job-manager-rest-api.php ├── class-wp-job-manager-shortcodes.php ├── class-wp-job-manager-usage-tracking-data.php ├── class-wp-job-manager-usage-tracking.php ├── class-wp-job-manager-widget.php ├── class-wp-job-manager.php ├── emails │ ├── class-wp-job-manager-email-admin-expiring-job.php │ ├── class-wp-job-manager-email-admin-new-job.php │ ├── class-wp-job-manager-email-admin-updated-job.php │ └── class-wp-job-manager-email-employer-expiring-job.php ├── forms │ ├── class-wp-job-manager-form-edit-job.php │ └── class-wp-job-manager-form-submit-job.php ├── helper │ ├── class-wp-job-manager-helper-api.php │ ├── class-wp-job-manager-helper-language-packs.php │ ├── class-wp-job-manager-helper-nonce.php │ ├── class-wp-job-manager-helper-options.php │ ├── class-wp-job-manager-helper-renewals.php │ ├── class-wp-job-manager-helper-rest-api.php │ ├── class-wp-job-manager-helper.php │ ├── class-wp-job-manager-site-trust-token.php │ └── views │ │ └── html-licenses.php ├── promoted-jobs │ ├── class-wp-job-manager-promoted-jobs-api.php │ ├── class-wp-job-manager-promoted-jobs-notifications.php │ ├── class-wp-job-manager-promoted-jobs-status-handler.php │ └── class-wp-job-manager-promoted-jobs.php ├── trait-singleton.php ├── ui │ ├── class-modal-dialog.php │ ├── class-notice.php │ ├── class-redirect-message.php │ ├── class-ui-elements.php │ ├── class-ui-settings.php │ └── class-ui.php └── widgets │ ├── class-wp-job-manager-widget-featured-jobs.php │ └── class-wp-job-manager-widget-recent-jobs.php ├── jest.config.js ├── languages └── wp-job-manager.pot ├── lib └── usage-tracking │ ├── class-wp-job-manager-usage-tracking-base.php │ └── tests │ ├── support │ ├── class-usage-tracking-test-subclass.php │ └── wp-die-exception.php │ └── test-class-usage-tracking.php ├── package-lock.json ├── package.json ├── phpcs.xml.dist ├── phpunit.xml.dist ├── psalm.xml ├── readme.md ├── readme.txt ├── renovate.json ├── scripts ├── RELEASE_PR_TEMPLATE.md.mjs ├── build-plugin.sh ├── create-release.mjs ├── exclude.lst ├── includes │ ├── chalk-lite.sh │ └── proceed_p.sh ├── prepare-release.mjs ├── prepare-release.sh ├── replace-next-version-tag.sh └── upload-and-link-plugin-zip.mjs ├── templates ├── access-denied-browse-job_listings.php ├── access-denied-single-job_listing.php ├── account-signin.php ├── content-job_listing.php ├── content-no-jobs-found.php ├── content-single-job_listing-company.php ├── content-single-job_listing-meta.php ├── content-single-job_listing.php ├── content-summary-job_listing.php ├── content-widget-job_listing.php ├── content-widget-no-jobs-found.php ├── emails │ ├── admin-expiring-job.php │ ├── admin-new-job.php │ ├── admin-updated-job.php │ ├── email-footer.php │ ├── email-header.php │ ├── email-job-details.php │ ├── email-styles.php │ ├── employer-expiring-job.php │ └── plain │ │ ├── admin-expiring-job.php │ │ ├── admin-new-job.php │ │ ├── admin-updated-job.php │ │ ├── email-footer.php │ │ ├── email-header.php │ │ ├── email-job-details.php │ │ └── employer-expiring-job.php ├── form-fields │ ├── checkbox-field.php │ ├── date-field.php │ ├── file-field.php │ ├── full-line-checkbox-field.php │ ├── multiselect-field.php │ ├── password-field.php │ ├── radio-field.php │ ├── recaptcha-field.php │ ├── recaptcha-v3-field.php │ ├── select-field.php │ ├── term-checklist-field.php │ ├── term-multiselect-field.php │ ├── term-select-field.php │ ├── text-field.php │ ├── textarea-field.php │ ├── uploaded-file-html.php │ └── wp-editor-field.php ├── job-application-email.php ├── job-application-url.php ├── job-application.php ├── job-dashboard-login.php ├── job-dashboard-overlay.php ├── job-dashboard.php ├── job-filter-job-types.php ├── job-filters.php ├── job-listings-end.php ├── job-listings-start.php ├── job-pagination.php ├── job-preview.php ├── job-stats.php ├── job-submit.php ├── job-submitted.php ├── notice.php └── pagination.php ├── tests ├── README.md ├── bin │ ├── get-wp-version.php │ ├── install-wp-tests.sh │ ├── phpcs.sh │ ├── prepare-wordpress.sh │ └── run-travis.sh └── php │ ├── bootstrap.php │ ├── includes │ ├── class-function-mocks.php │ ├── class-requests-transport-faker.php │ ├── class-wpjm-base-test.php │ ├── class-wpjm-rest-testcase.php │ ├── factories │ │ ├── class-wp-unittest-factory-for-job-listing.php │ │ └── class-wpjm-factory.php │ └── stubs │ │ ├── class-wp-job-manager-admin-settings-stub.php │ │ ├── class-wp-job-manager-email-invalid.php │ │ ├── class-wp-job-manager-email-template-valid.php │ │ ├── class-wp-job-manager-email-valid-secondary.php │ │ ├── class-wp-job-manager-email-valid.php │ │ ├── class-wp-job-manager-form-test.php │ │ ├── class-wpjm-ajax-action-stub.php │ │ ├── class-wpjm-api-handler-stub.php │ │ ├── plain-test-template.php │ │ └── test-template.php │ └── tests │ ├── includes │ ├── abstracts │ │ ├── test_class.wp-job-manager-email-template.php │ │ └── test_class.wp-job-manager-email.php │ ├── admin │ │ ├── test_class.notices-conditions-checker.php │ │ ├── test_class.wp-job-manager-admin-notices.php │ │ ├── test_class.wp-job-manager-cpt.php │ │ ├── test_class.wp-job-manager-settings.php │ │ └── test_class.wp-job-manager-writepanels.php │ ├── helper │ │ ├── test_class.wp-job-manager-helper-api.php │ │ ├── test_class.wp-job-manager-helper-options.php │ │ ├── test_class.wp-job-manager-helper.php │ │ ├── test_class.wp-job-manager-language-packs.php │ │ └── test_class.wp-job-manager-site-trust-token.php │ ├── rest-api │ │ ├── test_class.wp-job-manager-job-categories.php │ │ ├── test_class.wp-job-manager-job-listings.php │ │ └── test_class.wp-job-manager-job-types.php │ ├── test_class.access-token.php │ ├── test_class.guest-session.php │ ├── test_class.guest-user.php │ ├── test_class.stats.php │ ├── test_class.wp-job-manager-ajax.php │ ├── test_class.wp-job-manager-api.php │ ├── test_class.wp-job-manager-cache-helper.php │ ├── test_class.wp-job-manager-category-walker.php │ ├── test_class.wp-job-manager-data-cleaner.php │ ├── test_class.wp-job-manager-data-exporter.php │ ├── test_class.wp-job-manager-email-notifications.php │ ├── test_class.wp-job-manager-forms.php │ ├── test_class.wp-job-manager-geocode.php │ ├── test_class.wp-job-manager-post-types.php │ └── test_class.wp-job-manager-usage-tracking-data.php │ ├── test_class.wp-job-manager-functions.php │ └── test_class.wp-job-manager.php ├── uninstall.php ├── webpack.config.js ├── wp-job-manager-autoload.php ├── wp-job-manager-deprecated.php ├── wp-job-manager-functions.php ├── wp-job-manager-template.php ├── wp-job-manager.php └── wpml-config.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | indent_size = 4 13 | tab_width = 4 14 | indent_style = tab 15 | insert_final_newline = true 16 | trim_trailing_whitespace = true 17 | 18 | [*.txt] 19 | trim_trailing_whitespace = false 20 | 21 | [*.{md,json,yml}] 22 | trim_trailing_whitespace = false 23 | indent_style = space 24 | indent_size = 2 25 | 26 | [{*.txt}] 27 | end_of_line = crlf 28 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | assets/build 2 | node_modules 3 | tmp/ 4 | assets/lib/jquery-* 5 | assets/lib/select2 6 | assets/font 7 | vendor/ 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 'plugin:@wordpress/eslint-plugin/recommended', 'prettier' ], 3 | env: { 4 | browser: true, 5 | jquery: true, 6 | node: true, 7 | es6: true, 8 | }, 9 | globals: { 10 | wp: true, 11 | }, 12 | rules: { 13 | camelcase: 'warn', 14 | eqeqeq: 'warn', 15 | 'no-console': 'warn', 16 | '@wordpress/no-unused-vars-before-return': 'off', 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # .git-blame-ignore-revs 2 | # Re-format PHP files with PHPCBF 3 | 2dc64b85a9a640f4fa1878f4c2e2a1b34c85f432 4 | 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 14 | 15 | ### Steps to Reproduce 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | ### What I Expected 22 | 23 | 24 | ### What Happened Instead 25 | 26 | 27 | ### PHP / WordPress / WP Job Manager Version 28 | 29 | 30 | ### Browser / OS Version 31 | 32 | 33 | ### Screenshot / Video 34 | 35 | 36 | ### Context / Source 37 | 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-----security-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F46E‍♂️Security issue" 3 | about: Please report security issues *only* via https://www.hackerone.com 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | For security reasons, please report all security issues via https://hackerone.com/automattic/. Also, if the issue is valid, a bug bounty will be paid out to you. 11 | 12 | Please disclose responsibly and not via GitHub (which allows for exploiting issues in the wild before the patch is released). 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41E Bug report" 3 | about: Report a bug if something isn't working as expected in the core WP Job Manager 4 | plugin. 5 | title: '' 6 | labels: "[Type] Bug" 7 | assignees: '' 8 | 9 | --- 10 | 11 | 12 | 13 | **Describe the bug** 14 | A clear and concise description of what the bug is. Please be as descriptive as possible; issues lacking detail, or for any other reason than to report a bug, may be closed without action. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Expected behavior** 27 | A clear and concise description of what you expected to happen. 28 | 29 | **Isolating the problem (mark completed items with an [x]):** 30 | - [ ] I have deactivated other plugins and confirmed this bug occurs when only WP Job Manager plugin is active. 31 | - [ ] This bug happens with a default WordPress theme active. 32 | - [ ] I can reproduce this bug consistently using the steps above. 33 | 34 | **WordPress Environment** 35 | - WordPress Version: 36 | - WP Job Manager Version: 37 | - PHP Version: 38 | - Other important details: 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "✨ New Enhancement" 3 | about: If you have an idea for a new feature, an idea for an improvement on an existing one or you need something 4 | for development (such as a new hook) please let us know or submit a Pull Request! 5 | title: '' 6 | labels: "[Type] Enhancement" 7 | assignees: '' 8 | 9 | --- 10 | 11 | 12 | 13 | ### Is your feature request related to a problem? Please describe 14 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 15 | 16 | ### Describe the solution you'd like 17 | A clear and concise description of what you want to happen. 18 | 19 | ### Describe alternatives you've considered 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | ### Additional context 23 | Add any other context or screenshots about the feature request here. 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | ### Changes Proposed in this Pull Request 4 | 5 | * 6 | 7 | ### Testing Instructions 8 | 9 | * 10 | 11 | 12 | ### Release Notes 13 | 14 | * 15 | 16 | ### New or Updated Hooks and Templates 17 | 18 | 19 | * 20 | 21 | 22 | ### Deprecated Code 23 | 24 | * 25 | 26 | ### Screenshot / Video 27 | -------------------------------------------------------------------------------- /.github/RELEASE-CHECKLIST.yml: -------------------------------------------------------------------------------- 1 | paths: 2 | "**": 3 | - Follow the setup guide to create WPJM default pages. 4 | - Check that the installed version is correct on the Plugins page. 5 | - Create a new account and login with it. 6 | - Go to the 'Post a Job' page and post a new job. 7 | - Switch to the administrator and approve the newly posted job in admin. 8 | - Visit the 'Jobs' page with the non-admin user and make sure that the job is displayed. 9 | -------------------------------------------------------------------------------- /.github/git-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/WP-Job-Manager/41d5048c9cf6c4d2636c878419added8979fcdc6/.github/git-flow.png -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Plugin Build 2 | 3 | on: 4 | - pull_request 5 | 6 | jobs: 7 | build: 8 | name: Plugin Build 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Get npm cache directory 13 | id: npm-cache 14 | run: | 15 | echo "::set-output name=dir::$(npm config get cache)" 16 | - uses: actions/cache@v2 17 | with: 18 | path: ${{ steps.npm-cache.outputs.dir }} 19 | key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }} 20 | restore-keys: | 21 | ${{ runner.os }}-node- 22 | - uses: shivammathur/setup-php@v2 23 | with: 24 | php-version: '7.4' 25 | tools: composer 26 | coverage: none 27 | - name: Get composer cache directory 28 | id: composer-cache 29 | run: echo "::set-output name=dir::$(composer config cache-files-dir)" 30 | - uses: actions/cache@v2 31 | with: 32 | path: ${{ steps.composer-cache.outputs.dir }} 33 | key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }} 34 | restore-keys: | 35 | ${{ runner.os }}-composer- 36 | - name: Install JS dependencies 37 | run: npm ci 38 | - name: Install PHP dependencies 39 | run: composer install --no-ansi --no-interaction --prefer-dist --no-progress 40 | - name: Build Plugin 41 | run: npm run build 42 | - name: Decompress plugin 43 | run: unzip build/wp-job-manager.zip -d wp-job-manager 44 | - name: Store Artifact 45 | uses: actions/upload-artifact@v4 46 | with: 47 | name: wp-job-manager-${{ github.event.pull_request.head.sha }} 48 | path: ${{ github.workspace }}/wp-job-manager/ 49 | retention-days: 7 50 | - name: Save plugin zip and add link to the PR description 51 | env: 52 | WPJMCOM_API_LOGIN: ${{ secrets.WPJMCOM_API_LOGIN }} 53 | GH_TOKEN: ${{ github.token }} 54 | run: node scripts/upload-and-link-plugin-zip.mjs --pr ${{ github.event.number }} --commit ${{ github.event.pull_request.head.sha }} ${{ github.event.pull_request.merged && '--merged' || '' }} 55 | -------------------------------------------------------------------------------- /.github/workflows/close-issue-on-merge.yml: -------------------------------------------------------------------------------- 1 | name: Close issues related to a merged PR 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | 7 | jobs: 8 | closeIssueOnPrMergeTrigger: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Closes issues related to a merged pull request. 12 | uses: docker://ghcr.io/automattic/gha-mjolnir 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | name: Create Plugin Release 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - closed 7 | branches: 8 | - 'trunk' 9 | 10 | jobs: 11 | deploy: 12 | if: github.event.pull_request.merged == true && startsWith( github.head_ref, 'release/' ) 13 | runs-on: ubuntu-latest 14 | name: WPJM Release 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Comment on PR 18 | env: 19 | GITHUB_TOKEN: ${{ github.token }} 20 | run: gh pr comment ${{ github.event.number }} --body "🚀 **[Release workflow started](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})**" 21 | - uses: shivammathur/setup-php@v2 22 | with: 23 | php-version: '7.4' 24 | tools: composer 25 | coverage: none 26 | - name: Install JS dependencies 27 | run: npm ci 28 | - name: Install PHP dependencies 29 | run: composer install --no-ansi --no-interaction --prefer-dist --no-progress 30 | - name: Setup Git 31 | run: | 32 | git config user.name "WPJM Bot" 33 | git config user.email "" 34 | - name: Create Release 35 | id: create_release 36 | env: 37 | GITHUB_TOKEN: ${{ github.token }} 38 | run: | 39 | node scripts/create-release.mjs wp-job-manager ${{ github.event.number }} 40 | cd build && unzip -q wp-job-manager.zip 41 | - name: Deploy to WordPress.org 42 | uses: 10up/action-wordpress-plugin-deploy@abb939a0d0bfd01063e8d1933833209201557381 43 | env: 44 | SVN_PASSWORD: ${{ secrets.WORDPRESSORG_SVN_PASSWORD }} 45 | SVN_USERNAME: ${{ secrets.WORDPRESSORG_SVN_USERNAME }} 46 | BUILD_DIR: build/wp-job-manager 47 | SLUG: wp-job-manager 48 | VERSION: ${{ steps.create_release.outputs.version }} 49 | - name: Comment on PR 50 | env: 51 | GITHUB_TOKEN: ${{ github.token }} 52 | run: gh pr comment ${{ github.event.number }} --body "✅ Release **${{ steps.create_release.outputs.version }}** [deployed to WordPress.org](https://wordpress.org/plugins/wp-job-manager/)." 53 | -------------------------------------------------------------------------------- /.github/workflows/gardening.yml: -------------------------------------------------------------------------------- 1 | name: Repo gardening 2 | 3 | on: 4 | issues: 5 | types: [opened, reopened, edited, closed] 6 | issue_comment: 7 | types: [created] 8 | 9 | jobs: 10 | repo-gardening: 11 | name: 'Perform automated triage tasks on issues' 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 10 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup Node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 16 23 | 24 | - name: Wait for prior instances of the workflow to finish 25 | uses: softprops/turnstyle@v1 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | 29 | - name: 'Run gardening action' 30 | uses: automattic/action-repo-gardening@trunk 31 | with: 32 | github_token: ${{ secrets.GITHUB_TOKEN }} 33 | slack_token: ${{ secrets.SLACK_TOKEN }} 34 | slack_he_triage_channel: ${{ secrets.SLACK_HE_TRIAGE_CHANNEL }} 35 | slack_quality_channel: ${{ secrets.SLACK_QUALITY_CHANNEL }} 36 | tasks: 'gatherSupportReferences,replyToCustomersReminder' 37 | -------------------------------------------------------------------------------- /.github/workflows/github-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - trunk 7 | - workflow_dispatch 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: "Checkout" 14 | uses: actions/checkout@v4 15 | - name: Generate Swagger UI 16 | uses: Legion2/swagger-ui-action@v1 17 | with: 18 | output: swagger-ui 19 | spec-file: ./docs/api/internal.json 20 | version: "^4.18.0" 21 | - name: Deploy to GitHub Pages 22 | uses: peaceiris/actions-gh-pages@v3 23 | with: 24 | github_token: ${{ secrets.GITHUB_TOKEN }} 25 | publish_dir: swagger-ui 26 | destination_dir: api/internal 27 | -------------------------------------------------------------------------------- /.github/workflows/release-checklist.yml: -------------------------------------------------------------------------------- 1 | name: Plugin Release Build 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | branches: 8 | - 'trunk' 9 | 10 | jobs: 11 | checklist_job: 12 | if: ${{ startsWith( github.head_ref, 'release/' ) }} 13 | runs-on: ubuntu-latest 14 | name: WPJM release checklist 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | - name: Checklist 19 | uses: automattic/contextual-qa-checklist-action@master 20 | with: 21 | gh-token: ${{ secrets.GITHUB_TOKEN }} 22 | input-file: .github/RELEASE-CHECKLIST.yml 23 | comment-header: 'Please perform the following tests with the built package in a new installation before publishing:' 24 | comment-footer: '' 25 | show-paths: false 26 | -------------------------------------------------------------------------------- /.github/workflows/report-build.yml: -------------------------------------------------------------------------------- 1 | name: Report Plugin Build 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Plugin Build"] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | report-build: 11 | name: Report Plugin Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Publish commit status with the link to download the plugin 16 | id: plugin_artifact 17 | uses: Automattic/github-action-report-artifact@v0 18 | with: 19 | github-token: ${{ secrets.GITHUB_TOKEN }} 20 | artifact-name: wp-job-manager-${{ github.event.workflow_run.head_sha }} 21 | report-on: commit_status 22 | context: Plugin Build Download 23 | message: Click on "Details" to download the plugin zip file 24 | 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject/private/ 2 | /node_modules/ 3 | project.xml 4 | project.properties 5 | .DS_Store 6 | Thumbs.db 7 | .buildpath 8 | .project 9 | .settings* 10 | sftp-config.json 11 | /deploy/ 12 | /vendor/ 13 | /tmp/ 14 | /build/ 15 | /assets/dist/ 16 | /*.zip 17 | .idea 18 | .phpunit.result.cache 19 | 20 | # Ignore lib/wpjm_rest but keep folder 21 | lib/wpjm_rest/* 22 | !lib/wpjm_rest/.gitkeep 23 | tmp/mt 24 | 25 | # Ignore all log files except for .htaccess 26 | /logs/* 27 | !/logs/.htaccess 28 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "eqnull": true, 6 | "es3": true, 7 | "expr": true, 8 | "immed": true, 9 | "noarg": true, 10 | "nonbsp": true, 11 | "onevar": true, 12 | "quotmark": "single", 13 | "trailing": true, 14 | "undef": true, 15 | "unused": true, 16 | 17 | "browser": true, 18 | 19 | "globals": { 20 | "_": false, 21 | "Backbone": false, 22 | "jQuery": false, 23 | "JSON": false, 24 | "wp": false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | useTabs: true 2 | tabWidth: 2 3 | printWidth: 100 4 | singleQuote: true 5 | trailingComma: es5 6 | bracketSpacing: true 7 | parenSpacing: true 8 | jsxBracketSameLine: false 9 | semi: true 10 | arrowParens: avoid 11 | -------------------------------------------------------------------------------- /.psalm/psalm-loader.php: -------------------------------------------------------------------------------- 1 | * { 95 | flex: 1; 96 | white-space: nowrap; 97 | } 98 | } 99 | 100 | &__right { 101 | } 102 | &__visual { 103 | max-width: min(100%, 700px); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /assets/css/icons.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: job-manager; 3 | src: url(../font/job-manager.woff2?16367094); 4 | src: 5 | url(../font/job-manager.woff2?16367094) format('woff2'), 6 | url(../font/job-manager.svg?16367094#job-manager) format("svg"); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | @mixin jm-icon() { 12 | font-family: job-manager !important; 13 | font-style: normal; 14 | font-weight: normal; 15 | speak: none; 16 | 17 | display: inline-block; 18 | text-decoration: inherit; 19 | width: 1em; 20 | text-align: center; 21 | 22 | /* For safety - reset parent styles, that can break glyph codes*/ 23 | font-variant: normal; 24 | text-transform: none; 25 | 26 | /* fix buttons height, for twitter bootstrap */ 27 | line-height: 1em; 28 | } 29 | 30 | .jm-icon { 31 | 32 | @include jm-icon(); 33 | } 34 | -------------------------------------------------------------------------------- /assets/css/job-submission.scss: -------------------------------------------------------------------------------- 1 | #wp-link { 2 | 3 | #search-panel, 4 | #wplink-link-existing-content { 5 | display: none; 6 | } 7 | } 8 | 9 | div#wp-link-wrap.wp-core-ui { 10 | height: 300px; 11 | } 12 | 13 | .wplink-autocomplete.ui-autocomplete { 14 | display: none; 15 | visibility: hidden; 16 | } 17 | -------------------------------------------------------------------------------- /assets/css/menu.scss: -------------------------------------------------------------------------------- 1 | @import "icons"; 2 | 3 | /* Menu */ 4 | #adminmenu { 5 | 6 | #menu-posts-job_listing { 7 | 8 | .wp-menu-image::before { 9 | 10 | @include jm-icon(); 11 | content: "\e830"; 12 | } 13 | 14 | a[href="post-new.php?post_type=job_listing"] { 15 | display: none; 16 | } 17 | } 18 | 19 | #menu-posts-resume { 20 | 21 | .wp-menu-image::before { 22 | 23 | @include jm-icon(); 24 | content: "\e806"; 25 | } 26 | } 27 | 28 | .wpjm-addon-upsell { 29 | align-items: center; 30 | display: flex; 31 | justify-content: space-between; 32 | 33 | .wpjm-addon-upsell__badge { 34 | text-transform: uppercase; 35 | border-radius: 16px; 36 | color: inherit; 37 | opacity: 0.8; 38 | font-size: 8px; 39 | margin: 0; 40 | padding: 4px 6px; 41 | line-height: 8px; 42 | border: 1px solid currentColor; 43 | font-weight: 600; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /assets/css/setup.scss: -------------------------------------------------------------------------------- 1 | .help-page-link { 2 | cursor: help; 3 | } 4 | 5 | .wp-job-manager-setup-steps { 6 | margin: 1em 0 2em; 7 | overflow: hidden; 8 | 9 | li { 10 | width: 33.3%; 11 | padding: 7px 1em; 12 | font-weight: bold; 13 | margin: 0; 14 | color: #eee; 15 | background: #222; 16 | float: left; 17 | -moz-box-sizing: border-box; 18 | -webkit-box-sizing: border-box; 19 | box-sizing: border-box; 20 | 21 | &:first-child { 22 | margin-left: 0; 23 | } 24 | 25 | &.wp-job-manager-setup-active-step { 26 | background: #0074a2; 27 | color: #eee; 28 | } 29 | } 30 | } 31 | 32 | .wp-job-manager-shortcodes { 33 | 34 | td, 35 | th { 36 | vertical-align: middle; 37 | 38 | p { 39 | margin: 9px 0; 40 | } 41 | } 42 | 43 | tr:nth-child(even) { 44 | 45 | td, 46 | th { 47 | background: #f9f9f9; 48 | } 49 | } 50 | } 51 | 52 | .wp-job-manager-next-steps { 53 | font-size: 1.1em; 54 | list-style: disc inside; 55 | margin: 1.5em 2em; 56 | } 57 | 58 | .wp-job-manager-support-the-plugin { 59 | background: #fff; 60 | padding: 2em; 61 | margin: 2em 0; 62 | 63 | h3 { 64 | margin-top: 0; 65 | } 66 | 67 | ul { 68 | margin-bottom: 0; 69 | } 70 | 71 | li { 72 | line-height: 2em; 73 | font-size: 1.1em; 74 | 75 | a { 76 | text-decoration: none; 77 | } 78 | } 79 | 80 | li a::before { 81 | font-family: "dashicons"; 82 | font-size: 2em; 83 | vertical-align: middle; 84 | padding-right: 0.25em; 85 | } 86 | 87 | li.icon-review { 88 | 89 | a::before { 90 | content: "\f155"; 91 | } 92 | } 93 | 94 | li.icon-localization { 95 | 96 | a::before { 97 | content: "\f319"; 98 | } 99 | } 100 | 101 | li.icon-code { 102 | 103 | a::before { 104 | content: "\f111"; 105 | } 106 | } 107 | 108 | li.icon-forum { 109 | 110 | a::before { 111 | content: "\f328"; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /assets/css/ui.scss: -------------------------------------------------------------------------------- 1 | 2 | @import 'mixins'; 3 | 4 | @import 'ui.neutral'; 5 | @import 'ui.elements'; 6 | @import 'ui.form'; 7 | @import 'ui.notice'; 8 | @import 'ui.dialog'; 9 | 10 | -------------------------------------------------------------------------------- /assets/css/wpjm-brand.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --wpjm-brand-color-primary: #2404EB; 3 | --wpjm-brand-color-secondary: #EDD00E; 4 | --wpjm-brand-color-tertiary: #5842DE; 5 | --wpjm-brand-color-shade-1: #F1F4F9; 6 | --wpjm-brand-color-shade-2: #EBE8FF; 7 | --wpjm-brand-color-shade-3: #D9E1FF; 8 | --wpjm-brand-color-shade-4: #FFEF82; 9 | --wpjm-brand-color-shade-5: #FFFCE8; 10 | --wpjm-brand-sizing-scale: clamp(0px, (100vw - 400px) / (1200 - 400), 1px); 11 | --wpjm-brand-color-text: #1A1A1A; 12 | } 13 | 14 | @function fluid($desktop, $mobile) { 15 | @return calc(var(--wpjm-brand-sizing-scale) * (#{$desktop} - #{$mobile}) + #{$mobile}px); 16 | } 17 | 18 | .wpjm-button { 19 | 20 | border-radius: 2px; 21 | padding: 8px 12px; 22 | font-size: 14px; 23 | line-height: 20px; 24 | display: inline-flex; 25 | gap: 10px; 26 | align-items: center; 27 | justify-content: center; 28 | border: 1px solid transparent; 29 | text-decoration: none; 30 | white-space: nowrap; 31 | 32 | background: var(--wpjm-brand-color-primary); 33 | color: #ffffff; 34 | cursor: pointer; 35 | 36 | &:hover, &:active, &:focus { 37 | background: #1D00D0; 38 | color: #ffffff; 39 | box-shadow: unset; 40 | } 41 | &:active { 42 | background: #1E1E1E; 43 | color: #ffffff; 44 | } 45 | 46 | &:focus-visible { 47 | box-shadow: unset; 48 | outline: 1.5px solid var(--wp-admin-theme-color, var(--wpjm-brand-color-tertiary)); 49 | outline-offset: 1.5px; 50 | } 51 | 52 | &.is-outline { 53 | background: transparent; 54 | color: var(--wpjm-brand-color-primary); 55 | border-color: currentColor; 56 | 57 | &:hover { 58 | background: var(--wpjm-brand-color-shade-3); 59 | color: var(--wpjm-brand-color-primary); 60 | } 61 | &:active { 62 | border-color: #1E1E1E; 63 | color: #1E1E1E; 64 | } 65 | } 66 | 67 | &.is-link { 68 | background: transparent; 69 | color: var(--wpjm-brand-color-primary); 70 | 71 | &:hover { 72 | background: var(--wpjm-brand-color-shade-3); 73 | color: var(--wpjm-brand-color-primary); 74 | } 75 | } 76 | 77 | &.is-disabled { 78 | filter: grayscale(1); 79 | pointer-events: none; 80 | } 81 | 82 | } 83 | 84 | .wpjm-list-checkmarks { 85 | padding-left: 20px; 86 | list-style-image: url('data:image/svg+xml,%3csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'15\' height=\'11\' fill=\'none\' viewBox=\'0 0 15 11\'%3e%3cpath fill=\'black\' d=\'M5.03 10.6 0 5.55 1.06 4.5l3.97 3.97L13.5 0l1.06 1.06\'/%3e%3c/svg%3e'); 87 | display: flex; 88 | flex-direction: column; 89 | gap: 8px; 90 | 91 | li { 92 | padding-left: 8px; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /assets/font/job-manager.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/WP-Job-Manager/41d5048c9cf6c4d2636c878419added8979fcdc6/assets/font/job-manager.woff2 -------------------------------------------------------------------------------- /assets/images/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/WP-Job-Manager/41d5048c9cf6c4d2636c878419added8979fcdc6/assets/images/ajax-loader.gif -------------------------------------------------------------------------------- /assets/images/company.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/WP-Job-Manager/41d5048c9cf6c4d2636c878419added8979fcdc6/assets/images/company.png -------------------------------------------------------------------------------- /assets/images/icons/checkmark-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/jm-full-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/WP-Job-Manager/41d5048c9cf6c4d2636c878419added8979fcdc6/assets/images/jm-full-logo.png -------------------------------------------------------------------------------- /assets/images/wpjm-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/WP-Job-Manager/41d5048c9cf6c4d2636c878419added8979fcdc6/assets/images/wpjm-logo.png -------------------------------------------------------------------------------- /assets/js/admin/editor-lifecycle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | import { subscribe, select } from '@wordpress/data'; 5 | import { store as editorStore } from '@wordpress/editor'; 6 | 7 | /** 8 | * Helper function to fire callbacks on editor lifecycles. 9 | * 10 | * @param {Object} options 11 | * @param {Function} options.subscribeListener Callback called everytime the subscribe listener is called. 12 | * @param {Function} options.onSetDirty Callback called when the editor becomes dirty. 13 | * @param {Function} options.onSaveStart Callback called when editor starts saving. 14 | * @param {Function} options.onSave Callback called when a save is completed. 15 | * 16 | * @return {Function} Unsubscribe function. 17 | */ 18 | const editorLifecycle = ( { 19 | subscribeListener = () => {}, 20 | onSetDirty = () => {}, 21 | onSaveStart = () => {}, 22 | onSave = () => {}, 23 | } ) => { 24 | const coreEditorSelector = select( editorStore ); 25 | let wasSaving = false; 26 | let wasDirty = false; 27 | 28 | const unsubscribe = subscribe( () => { 29 | subscribeListener(); 30 | 31 | const isDirty = coreEditorSelector.isEditedPostDirty(); 32 | 33 | const isSaving = 34 | coreEditorSelector.isSavingPost() && 35 | ! coreEditorSelector.isAutosavingPost(); 36 | 37 | if ( ! wasDirty && isDirty ) { 38 | // If editor becomes dirty. 39 | wasDirty = true; 40 | onSetDirty(); 41 | } else { 42 | wasDirty = isDirty; 43 | } 44 | 45 | if ( wasSaving && ! isSaving ) { 46 | // If it completed a saving. 47 | wasSaving = isSaving; 48 | onSave(); 49 | } else if ( ! wasSaving && isSaving ) { 50 | // If it started saving. 51 | wasSaving = isSaving; 52 | onSaveStart(); 53 | } else { 54 | wasSaving = isSaving; 55 | } 56 | } ); 57 | 58 | return unsubscribe; 59 | }; 60 | 61 | export default editorLifecycle; 62 | -------------------------------------------------------------------------------- /assets/js/admin/job-editor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | import { select } from '@wordpress/data'; 5 | import { store as editorStore } from '@wordpress/editor'; 6 | import domReady from '@wordpress/dom-ready'; 7 | 8 | /** 9 | * Internal dependencies 10 | */ 11 | import editorLifecycle from './editor-lifecycle'; 12 | import { postOpenPromoteModal } from './promote-job-modals'; 13 | 14 | // Open promote dialog when it's publishing a job. 15 | domReady( () => { 16 | const coreEditorSelector = select( editorStore ); 17 | const promoteDialog = document.querySelector( '#promote-dialog' ); 18 | 19 | if ( ! promoteDialog ) { 20 | return; 21 | } 22 | 23 | let jobWasPublished = false; 24 | 25 | editorLifecycle( { 26 | onSaveStart: () => { 27 | // Mark if status is being changed to publish. 28 | jobWasPublished = 29 | 'publish' === coreEditorSelector.getEditedPostAttribute( 'status' ) && 30 | 'publish' !== coreEditorSelector.getCurrentPostAttribute( 'status' ); 31 | }, 32 | onSave: () => { 33 | const meta = coreEditorSelector.getCurrentPostAttribute( 'meta' ); 34 | 35 | // Open dialog when job was published and it's not promoted. 36 | if ( jobWasPublished && '1' !== meta?._promoted ) { 37 | promoteDialog.showModal(); 38 | postOpenPromoteModal( promoteDialog, window.wpjm.promoteUrl ); 39 | } 40 | }, 41 | } ); 42 | } ); 43 | -------------------------------------------------------------------------------- /assets/js/admin/job-tags-upsell.js: -------------------------------------------------------------------------------- 1 | import { registerPlugin } from '@wordpress/plugins'; 2 | import { PluginDocumentSettingPanel } from '@wordpress/edit-post'; 3 | import { ExternalLink } from '@wordpress/components'; 4 | import { __ } from '@wordpress/i18n'; 5 | 6 | const PROMO_LINK = 'https://wpjobmanager.com/add-ons/job-tags/?utm_source=plugin_wpjm&utm_medium=upsell&utm_campaign=job_tags_editor_upsell'; 7 | 8 | const WPJM_Job_Tags_Upsell_Panel = () => ( 9 | 14 |

15 | { __( 'Improve job listings by adding Job Tags, which can include skills, interests, technologies, and more.', 'wp-job-manager' ) } 16 |

17 | 18 | { __( 'Get Job Tags', 'wp-job-manager' ) } 19 | 20 |
21 | ) 22 | 23 | registerPlugin('plugin-document-setting-panel-demo', { 24 | render: WPJM_Job_Tags_Upsell_Panel 25 | }) 26 | -------------------------------------------------------------------------------- /assets/js/admin/promote-job-modals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal dependencies 3 | */ 4 | import wpjmModal from './wpjm-modal'; 5 | 6 | export const postOpenPromoteModal = ( dialog, href ) => { 7 | dialog.innerHTML = ` 8 |
9 | 10 |
11 | 12 | 17 | `; 18 | 19 | dialog.querySelector( '#wpjm-promote-button' ).addEventListener( 'click', function() { 20 | dialog.close(); 21 | } ); 22 | }; 23 | 24 | export const postOpenDeactivateModal = ( dialog, href ) => { 25 | const deactivateButton = dialog.querySelector( '.deactivate-promotion' ); 26 | deactivateButton.setAttribute( 'href', href ); 27 | }; 28 | 29 | export const initializePromoteModals = () => { 30 | wpjmModal( '.promote_job', '#promote-dialog', ( element, dialog ) => { 31 | const href = element.getAttribute( 'data-href' ); 32 | postOpenPromoteModal( dialog, href ); 33 | } ); 34 | wpjmModal( '.jm-promoted__deactivate', '#deactivate-dialog', ( element, dialog ) => { 35 | const href = element.getAttribute( 'data-href' ); 36 | postOpenDeactivateModal( dialog, href ); 37 | } ); 38 | 39 | customElements.define( 'promote-job-template', 40 | class extends HTMLElement { 41 | constructor() { 42 | super(); 43 | const promoteJobs = document.getElementById( 'promote-job-template' ).content; 44 | const shadowRoot = this.attachShadow( { 45 | mode: 'open', 46 | } ); 47 | shadowRoot.appendChild( promoteJobs.cloneNode( true ) ); 48 | } 49 | } ); 50 | } 51 | -------------------------------------------------------------------------------- /assets/js/admin/wpjm-modal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Job Manager admin modal. 3 | * 4 | * @param {string} selector 5 | * @param {string} dialogSelector 6 | * @param {Function} openCallback 7 | */ 8 | const wpjmModal = ( selector, dialogSelector, openCallback ) => { 9 | const elements = document.querySelectorAll( selector ); 10 | const dialog = document.querySelector( dialogSelector ); 11 | 12 | elements.forEach( ( element ) => { 13 | element.addEventListener( 'click', function( event ) { 14 | event.preventDefault(); 15 | dialog.showModal(); 16 | openCallback( element, dialog ); 17 | } ); 18 | } ); 19 | } 20 | 21 | export default wpjmModal; 22 | -------------------------------------------------------------------------------- /assets/js/admin/wpjm-notice-dismiss.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | import domReady from '@wordpress/dom-ready'; 5 | 6 | domReady( () => { 7 | 8 | /** 9 | * Handle dismissing the notice by sending a request to the server. 10 | * 11 | * @param element The DOM element of the container of the notice being dismissed. 12 | */ 13 | const handleDismiss = async( element ) => { 14 | const formData = new FormData(); 15 | if ( element.dataset.dismissNotice ) { 16 | formData.append( 'notice', element.dataset.dismissNotice ); 17 | } 18 | formData.append( 'action', element.dataset.dismissAction ); 19 | formData.append( 'nonce', element.dataset.dismissNonce ); 20 | 21 | fetch( ajaxurl, { 22 | method: 'POST', 23 | body: formData, 24 | } ); 25 | 26 | await element.animate( [ { opacity: 0 } ], 100 ).finished; 27 | await element.animate( 28 | [ { opacity: 0 }, { 29 | opacity: 0, 30 | height: 0, 31 | paddingTop: 0, 32 | paddingBottom: 0, 33 | } ], 100, 34 | ).finished; 35 | element.remove(); 36 | 37 | }; 38 | 39 | const wpjmNotices = document.querySelectorAll( '.wpjm-admin-notice, .wpjm-admin-modal-notice' ); 40 | for ( const wpjmNotice of wpjmNotices ) { 41 | wpjmNotice.addEventListener( 'click', ( event ) => { 42 | 43 | if ( 44 | wpjmNotice.dataset.dismissNonce && 45 | wpjmNotice.dataset.dismissAction && 46 | event.target.classList.contains( 'wpjm-notice-dismiss' ) 47 | ) { 48 | handleDismiss( wpjmNotice ); 49 | } 50 | return true; 51 | } ); 52 | } 53 | } ); 54 | -------------------------------------------------------------------------------- /assets/js/datepicker.js: -------------------------------------------------------------------------------- 1 | /* global job_manager_datepicker */ 2 | jQuery(document).ready( function() { 3 | if ( jQuery.datepicker._defaults.dateFormat == '' ) { 4 | jQuery.datepicker._defaults.dateFormat = 'MM d, yy'; 5 | } 6 | var $date_today = new Date(); 7 | var datePickerOptions = { 8 | altFormat : 'yy-mm-dd', 9 | minDate : $date_today, 10 | }; 11 | 12 | if ( typeof job_manager_datepicker !== 'undefined' ) { 13 | datePickerOptions.dateFormat = job_manager_datepicker.date_format; 14 | } 15 | 16 | var initializeDatepicker = function ( targetInput ) { 17 | var $target = jQuery( targetInput ); 18 | var $hidden_input = jQuery( '', { type: 'hidden', name: $target.attr( 'name' ) } ).insertAfter( $target ); 19 | 20 | $target.attr( 'name', $target.attr( 'name' ) + '-datepicker' ); 21 | $target.on( 'keyup', function() { 22 | if ( '' === $target.val() ) { 23 | $hidden_input.val( '' ); 24 | } 25 | } ); 26 | $target.datepicker( jQuery.extend( {}, datePickerOptions, { altField: $hidden_input } ) ); 27 | if ( $target.val() ) { 28 | var dateParts = $target.val().split('-'); 29 | if ( 3 === dateParts.length ) { 30 | var selectedDate = new Date(parseInt(dateParts[0], 10), (parseInt(dateParts[1], 10) - 1), parseInt(dateParts[2], 10)); 31 | $target.datepicker('setDate', selectedDate); 32 | } 33 | } 34 | }; 35 | 36 | jQuery( 'input.job-manager-datepicker, input#_job_expires' ).each( function() { 37 | initializeDatepicker( this ); 38 | } ); 39 | 40 | 41 | jQuery( document ).on( 'wpJobManagerFieldAdded', function ( e ) { 42 | initializeDatepicker( e.target ); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /assets/js/job-application.js: -------------------------------------------------------------------------------- 1 | jQuery(document).ready(function($) { 2 | // Slide toggle 3 | if ( ! $( 'body' ).hasClass( 'job-application-details-keep-open' ) ) { 4 | $( '.application_details' ).hide(); 5 | } 6 | 7 | $( document.body ).on( 'click', '.job_application .application_button', function() { 8 | var $details = $(this).parents('.job_application').find('.application_details').first(); 9 | var $button = $(this); 10 | $details.slideToggle( 400, function() { 11 | if ( ! $(this).is(':visible') ) { 12 | // Only care if we toggled to be visible 13 | return; 14 | } 15 | 16 | $details.trigger('visible'); 17 | 18 | // If max(33% height, 200px) of the application details aren't shown, scroll. 19 | var minimum_details_threshold = Math.max( Math.min( $details.outerHeight(), 200 ), $details.outerHeight() * .33 ); 20 | var details_visible_threshold = $details.offset().top + minimum_details_threshold; 21 | var nice_buffer = 5; 22 | var top_viewport_buffer = nice_buffer; 23 | // We can't account for all theme headers with a fixed position on the top, but we can at least account for #wpadminbar and a fixed
24 | if ( $( '#wpadminbar' ).length > 0 && 'fixed' === $( '#wpadminbar' ).css( 'position' ) ) { 25 | top_viewport_buffer += $( '#wpadminbar' ).outerHeight(); 26 | } 27 | if ( $( 'header' ).length > 0 && 'fixed' === $( 'header' ).css( 'position' ) ) { 28 | top_viewport_buffer += $( 'header' ).outerHeight(); 29 | } 30 | var bottom_of_screen = $(window).scrollTop() + window.innerHeight; 31 | var amount_hidden = $details.offset().top + $details.outerHeight() - bottom_of_screen; 32 | var window_height = window.innerHeight - top_viewport_buffer; 33 | 34 | if ( amount_hidden > 0 && $details.outerHeight() < ( window_height * .9 ) ) { 35 | // Application contents are shorter than the 90% of viewport, just scroll to show the bottom of details (with `nice_buffer` buffer) 36 | $('html, body').animate( { scrollTop: $(window).scrollTop() + amount_hidden + nice_buffer }, 400 ); 37 | } else if( bottom_of_screen < details_visible_threshold ){ 38 | // The application box is larger than the viewport AND our `minimum_details_threshold` is not visible. 39 | // Scroll to show top of application button, showing top of details 40 | $('html, body').animate( { scrollTop: $button.offset().top - top_viewport_buffer }, 600 ); 41 | } 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /assets/js/job-dashboard.js: -------------------------------------------------------------------------------- 1 | /* global job_manager_job_dashboard */ 2 | 3 | import domReady from '@wordpress/dom-ready'; 4 | 5 | // eslint-disable-next-line camelcase 6 | const { i18nConfirmDelete, overlayEndpoint, statsEnabled } = job_manager_job_dashboard; 7 | 8 | function setupEvents( root ) { 9 | root 10 | .querySelectorAll( '.job-dashboard-action-delete' ) 11 | .forEach( el => el.addEventListener( 'click', confirmDelete ) ); 12 | } 13 | 14 | function confirmDelete( event ) { 15 | // eslint-disable-next-line no-alert 16 | if ( ! window.confirm( i18nConfirmDelete ) ) { 17 | event.preventDefault(); 18 | } 19 | } 20 | 21 | async function showOverlay( eventOrId ) { 22 | const overlayDialog = document.getElementById( 'jmDashboardOverlay' ); 23 | 24 | if ( ! overlayDialog ) { 25 | return true; 26 | } 27 | 28 | eventOrId.preventDefault?.(); 29 | overlayDialog.showModal(); 30 | 31 | const dashboardRowId = eventOrId.target?.dataset.jobId; 32 | const adminRowId = eventOrId.target?.closest( 'tr' )?.id?.replace( 'post-', '' ); 33 | 34 | const id = dashboardRowId ?? adminRowId ?? eventOrId; 35 | 36 | if ( ! id ) { 37 | return; 38 | } 39 | 40 | location.hash = id; 41 | 42 | const contentElement = overlayDialog.querySelector( '.jm-dialog-modal-content' ); 43 | contentElement.innerHTML = ''; 44 | 45 | try { 46 | const response = await fetch( `${ overlayEndpoint }&job_id=${ id }` ); 47 | 48 | if ( ! response.ok ) { 49 | throw new Error( response.statusText ); 50 | } 51 | 52 | const { data } = await response.json(); 53 | 54 | contentElement.innerHTML = data; 55 | } catch ( error ) { 56 | contentElement.innerHTML = `
${ error.message }
`; 57 | } 58 | 59 | const clearHash = () => { 60 | history.replaceState( null, '', window.location.pathname + window.location.search ); 61 | overlayDialog.removeEventListener( 'close', clearHash ); 62 | }; 63 | 64 | overlayDialog.addEventListener( 'close', clearHash ); 65 | 66 | setupEvents( contentElement ); 67 | } 68 | 69 | function setupStatsOverlay() { 70 | document 71 | .querySelectorAll( '.jm-dashboard-job .job-title, tr.job_listing td.column-stats' ) 72 | .forEach( el => el.addEventListener( 'click', showOverlay ) ); 73 | 74 | const urlHash = window.location.hash?.substring( 1 ); 75 | 76 | if ( urlHash > 0 ) { 77 | showOverlay( +urlHash ); 78 | } 79 | } 80 | 81 | domReady( () => { 82 | setupEvents( document ); 83 | 84 | if ( statsEnabled ) { 85 | setupStatsOverlay(); 86 | } 87 | } ); 88 | -------------------------------------------------------------------------------- /assets/js/multiselect.js: -------------------------------------------------------------------------------- 1 | /* global job_manager_select2_args */ 2 | jQuery( function( $ ) { 3 | if ( $.isFunction( $.fn.select2 ) && typeof job_manager_select2_args !== 'undefined' ) { 4 | $( '.job-manager-multiselect:visible' ).select2( job_manager_select2_args ); 5 | } 6 | } ); 7 | -------------------------------------------------------------------------------- /assets/js/stats/observers.js: -------------------------------------------------------------------------------- 1 | export function waitForSelector( selector ) { 2 | return new Promise( function ( resolve ) { 3 | let node = document.querySelector( selector ); 4 | if ( node ) { 5 | return resolve( node ); 6 | } 7 | 8 | const observer = new MutationObserver( function () { 9 | node = document.querySelector( selector ); 10 | if ( node ) { 11 | observer.disconnect(); 12 | resolve( node ); 13 | } 14 | } ); 15 | 16 | observer.observe( document.documentElement, { 17 | childList: true, 18 | subtree: true, 19 | } ); 20 | } ); 21 | } 22 | -------------------------------------------------------------------------------- /assets/js/stats/unique.js: -------------------------------------------------------------------------------- 1 | import { requestIdleCallback } from './utils'; 2 | 3 | const EXPIRATION = 24 * 60 * 60 * 1000; 4 | const PREFIX = 'wpjm-stat-'; 5 | 6 | export function setAsRecordedToday( stat ) { 7 | const key = `${ PREFIX }${ stat.unique_key }`; 8 | const expiresAtTimestamp = Date.now() + EXPIRATION; 9 | window.localStorage[ key ] = expiresAtTimestamp; 10 | } 11 | 12 | export function wasRecordedToday( stat ) { 13 | const key = `${ PREFIX }${ stat.unique_key }`; 14 | return ! isExpired( window.localStorage[ key ] ); 15 | } 16 | 17 | function isExpired( record ) { 18 | if ( ! record ) { 19 | return true; 20 | } 21 | const expiration = parseInt( record, 10 ); 22 | return Number.isNaN( expiration ) ? true : expiration < Date.now(); 23 | } 24 | 25 | export function isUnique( stat ) { 26 | return !! stat.unique_key; 27 | } 28 | 29 | export function filterAndRecordUniques( stats ) { 30 | return stats.filter( stat => { 31 | if ( isUnique( stat ) ) { 32 | if ( wasRecordedToday( stat ) ) { 33 | return false; 34 | } 35 | setAsRecordedToday( stat ); 36 | } 37 | return true; 38 | } ); 39 | } 40 | 41 | export function scheduleStaleUniqueCleanup() { 42 | const cleanup = function () { 43 | for ( let i = 0; i < localStorage.length; i++ ) { 44 | const key = localStorage.key( i ); 45 | 46 | if ( ! key.startsWith( PREFIX ) ) { 47 | continue; 48 | } 49 | 50 | if ( isExpired( localStorage.getItem( key ) ) ) { 51 | localStorage.removeItem( key ); 52 | } 53 | } 54 | }; 55 | 56 | requestIdleCallback( cleanup ); 57 | } 58 | -------------------------------------------------------------------------------- /assets/js/stats/utils.js: -------------------------------------------------------------------------------- 1 | export function debounce( func, delay ) { 2 | let timeoutId; 3 | 4 | return function ( ...args ) { 5 | clearTimeout( timeoutId ); 6 | 7 | timeoutId = setTimeout( () => { 8 | func( ...args ); 9 | }, delay ); 10 | }; 11 | } 12 | 13 | export function getPostId( node ) { 14 | return +node.className.match( /\bpost-(\d+)\b/ )?.[ 1 ] || 0; 15 | } 16 | 17 | export function indexBy( list, key ) { 18 | return list.reduce( function ( accum, item ) { 19 | if ( ! item[ key ] ) { 20 | return accum; 21 | } 22 | accum[ item[ key ] ] = item; 23 | return accum; 24 | }, {} ); 25 | } 26 | 27 | export const requestIdleCallback = window.requestIdleCallback 28 | ? fn => window.requestIdleCallback( fn, { timeout: 500 } ) 29 | : fn => setTimeout( fn, 100 ); 30 | -------------------------------------------------------------------------------- /assets/js/term-multiselect.js: -------------------------------------------------------------------------------- 1 | /* global job_manager_select2_args */ 2 | jQuery( function( $ ) { 3 | if ( $.isFunction( $.fn.select2 ) && typeof job_manager_select2_args !== 'undefined' ) { 4 | $( '.job-manager-category-dropdown:visible' ).select2( job_manager_select2_args ); 5 | } 6 | } ); 7 | -------------------------------------------------------------------------------- /assets/js/tests/empty.js: -------------------------------------------------------------------------------- 1 | test( 'truth is truth', () => { 2 | expect( true ).toBe( true ); 3 | } ); 4 | -------------------------------------------------------------------------------- /assets/js/ui.color-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parse a CSS color string and provide RGB and HSL values. 3 | * 4 | * Supported formats: "rgb(255, 255, 255)" or "rgba(255, 255, 255, 0.5)" 5 | * 6 | * Use as Color.parse( rgbString ) 7 | */ 8 | export class Color { 9 | /** 10 | * Create a new Color instance. 11 | * 12 | * @param {Object} rgb RGB values ( { r: 255, g: 255, b: 255, a: 1.0 } ) 13 | */ 14 | constructor( rgb ) { 15 | Object.assign( this, rgb ); 16 | Object.assign( this, Color.toHsl( rgb ) ); 17 | } 18 | 19 | /** 20 | * Parse a CSS color string and return a new Color instance. 21 | * 22 | * @param {string} rgbString CSS color string. 23 | * 24 | * @return {?Color} Color instance or null if parsing failed. 25 | */ 26 | static parse( rgbString ) { 27 | const match = rgbString?.match?.( /(\d+)/g ); 28 | 29 | if ( ! match || match.length < 3 ) { 30 | return null; 31 | } 32 | 33 | const [ r, g, b, a ] = match; 34 | return new Color( { r: +r, g: +g, b: +b, a: a ? +a : 1.0 } ); 35 | } 36 | 37 | /** 38 | * Convert RGB to HSL. 39 | * 40 | * @param {{r: number, g: number, b: number, a: number}} rgb RGB values. 41 | * @return {{a: number, s: number, h: number, l: number}} HSL values. 42 | */ 43 | static toHsl( rgb ) { 44 | let { r, g, b, a } = rgb; 45 | r /= 255; 46 | g /= 255; 47 | b /= 255; 48 | 49 | const max = Math.max( r, g, b ); 50 | const min = Math.min( r, g, b ); 51 | let h, s; 52 | const l = ( max + min ) / 2; 53 | 54 | if ( max === min ) { 55 | h = s = 0; 56 | } else { 57 | const d = max - min; 58 | s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min ); 59 | 60 | switch ( max ) { 61 | case r: 62 | h = ( g - b ) / d + ( g < b ? 6 : 0 ); 63 | break; 64 | case g: 65 | h = ( b - r ) / d + 2; 66 | break; 67 | case b: 68 | h = ( r - g ) / d + 4; 69 | break; 70 | } 71 | 72 | h /= 6; 73 | } 74 | 75 | return { h, s, l, a: +a }; 76 | } 77 | 78 | /** 79 | * Render as CSS color string. 80 | */ 81 | get css() { 82 | const { r, g, b, a } = this; 83 | return `rgba(${ r }, ${ g }, ${ b }, ${ a })`; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /assets/js/wpjm-stats.js: -------------------------------------------------------------------------------- 1 | import domReady from '@wordpress/dom-ready'; 2 | import { scheduleStaleUniqueCleanup, filterAndRecordUniques } from './stats/unique'; 3 | import { initImpressionStat } from './stats/impressions'; 4 | import { requestIdleCallback } from './stats/utils'; 5 | 6 | const WPJMStats = { 7 | stats: [], 8 | actions: [], 9 | init( stats ) { 10 | WPJMStats.stats = stats; 11 | 12 | stats.forEach( stat => { 13 | WPJMStats.types[ stat.type ]?.( stat ); 14 | } ); 15 | 16 | WPJMStats.doAction( 'page-load' ); 17 | scheduleStaleUniqueCleanup(); 18 | }, 19 | 20 | doAction( action ) { 21 | const stats = WPJMStats.actions[ action ]; 22 | if ( stats ) { 23 | WPJMStats.log( stats ); 24 | } 25 | }, 26 | types: { 27 | action( stat ) { 28 | const { action } = stat; 29 | if ( ! WPJMStats.actions[ action ] ) { 30 | WPJMStats.actions[ action ] = []; 31 | } 32 | WPJMStats.actions[ action ].push( stat ); 33 | }, 34 | domEvent( stat ) { 35 | const { args } = stat; 36 | if ( args.element && args.event ) { 37 | const domElement = document.querySelector( args.element ); 38 | const handler = stat.action 39 | ? () => WPJMStats.doAction( stat.action ) 40 | : () => WPJMStats.log( stat ); 41 | domElement?.addEventListener( args.event, handler ); 42 | } 43 | }, 44 | impression: initImpressionStat, 45 | }, 46 | async log( stats ) { 47 | if ( stats.name ) { 48 | stats = [ stats ]; 49 | } 50 | 51 | const { ajaxUrl, ajaxNonce, postId } = window.job_manager_stats; 52 | 53 | stats = filterAndRecordUniques( stats ); 54 | 55 | if ( ! stats.length ) { 56 | return false; 57 | } 58 | 59 | const payload = stats.map( stat => { 60 | return [ 'name', 'group', 'post_id' ].reduce( ( m, field ) => { 61 | if ( stat[ field ] ) { 62 | m[ field ] = stat[ field ]; 63 | } 64 | return m; 65 | }, {} ); 66 | } ); 67 | 68 | const postData = new URLSearchParams( { 69 | _ajax_nonce: ajaxNonce, 70 | post_id: postId, 71 | action: 'job_manager_log_stat', 72 | stats: JSON.stringify( payload ), 73 | } ); 74 | 75 | return fetch( ajaxUrl, { 76 | method: 'POST', 77 | credentials: 'same-origin', 78 | body: postData, 79 | } ); 80 | }, 81 | }; 82 | 83 | window.WPJMStats = WPJMStats; 84 | 85 | domReady( () => { 86 | requestIdleCallback( () => { 87 | const { stats } = window.job_manager_stats; 88 | WPJMStats.init( stats ); 89 | } ); 90 | } ); 91 | -------------------------------------------------------------------------------- /assets/lib/jquery-chosen/images/chosen-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/WP-Job-Manager/41d5048c9cf6c4d2636c878419added8979fcdc6/assets/lib/jquery-chosen/images/chosen-sprite.png -------------------------------------------------------------------------------- /assets/lib/jquery-chosen/images/chosen-sprite@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/WP-Job-Manager/41d5048c9cf6c4d2636c878419added8979fcdc6/assets/lib/jquery-chosen/images/chosen-sprite@2x.png -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "automattic/wp-job-manager", 3 | "description": "WP Job Manager is a lightweight job listing plugin for adding job-board like functionality to your WordPress site. Being shortcode based, it can work with any theme and is really simple to setup.", 4 | "homepage" : "http://wpjobmanager.com/", 5 | "type" : "wordpress-plugin", 6 | "license" : "GPL-3.0+", 7 | "require-dev" : { 8 | "php": "^7.4 || ^8.0", 9 | "phpunit/phpunit": "^9.0", 10 | "dealerdirect/phpcodesniffer-composer-installer": "1.0.0", 11 | "phpcompatibility/php-compatibility": "^9.3.5", 12 | "phpcompatibility/phpcompatibility-wp": "2.1.4", 13 | "squizlabs/php_codesniffer": "3.7.2", 14 | "wp-coding-standards/wpcs": "2.3.0", 15 | "sirbrillig/phpcs-variable-analysis": "^2.6", 16 | "yoast/phpunit-polyfills": "^1.0.2", 17 | "vimeo/psalm": "^5.13", 18 | "php-stubs/wordpress-stubs": "^6.4" 19 | }, 20 | "config": { 21 | "allow-plugins": { 22 | "dealerdirect/phpcodesniffer-composer-installer": true 23 | }, 24 | "platform": { 25 | "php": "7.4" 26 | } 27 | }, 28 | "require": { 29 | "pelago/emogrifier": "^7.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | API docs 2 | ======== 3 | This folder contains documentation for our publicly exposed APIs. 4 | 5 | ## Internal APIs 6 | We consider that an API is _internal_ when it is not expected to be used by any third-party. We will not provide any 7 | support for these APIs, and they are subject to change **without any previous warning**. 8 | - [OpenAPI spec](internal.json) 9 | - [Swagger UI](https://automattic.github.io/wp-job-manager/api/internal/) 10 | -------------------------------------------------------------------------------- /includes/3rd-party/3rd-party.php: -------------------------------------------------------------------------------- 1 | $post ) { 16 | if ( $post instanceof WP_Post && \WP_Job_Manager_Post_Types::PT_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 ); 26 | -------------------------------------------------------------------------------- /includes/3rd-party/jetpack.php: -------------------------------------------------------------------------------- 1 | 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[] = \WP_Job_Manager_Post_Types::PT_LISTING; 36 | return $post_types; 37 | } 38 | add_filter( 'jetpack_sitemap_post_types', 'wpjm_jetpack_add_post_type' ); 39 | -------------------------------------------------------------------------------- /includes/3rd-party/polylang.php: -------------------------------------------------------------------------------- 1 | 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 ( \WP_Job_Manager_Post_Types::PT_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 ( \WP_Job_Manager_Post_Types::PT_LISTING === $post->post_type ) { 53 | $weight = 100; 54 | } 55 | return $weight; 56 | } 57 | -------------------------------------------------------------------------------- /includes/3rd-party/wp-all-import.php: -------------------------------------------------------------------------------- 1 | maybe_add_default_meta_data( $post_id, get_post( $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 | } 26 | -------------------------------------------------------------------------------- /includes/3rd-party/yoast.php: -------------------------------------------------------------------------------- 1 | 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 ); 30 | 31 | /** 32 | * Links schema to Yoast SEO schema if Yoast SEO is loaded. 33 | * 34 | * @param array $data The schema data. 35 | * 36 | * @return array The schema data. 37 | */ 38 | function wpjm_link_schema_to_yoast_schema( $data ) { 39 | if ( function_exists( 'YoastSEO' ) ) { 40 | $data['mainEntityOfPage'] = [ '@id' => YoastSEO()->meta->for_current_page()->canonical ]; 41 | $data['identifier']['value'] = YoastSEO()->meta->for_current_page()->canonical; 42 | } 43 | return $data; 44 | } 45 | add_filter( 'wpjm_get_job_listing_structured_data', 'wpjm_link_schema_to_yoast_schema' ); 46 | -------------------------------------------------------------------------------- /includes/admin/views/html-admin-notice-core-setup.php: -------------------------------------------------------------------------------- 1 | 12 |
13 |

14 | WP Job Manager.', 'wp-job-manager' ) ); 16 | ?> 17 |

18 |

19 | 20 | 21 |

22 |
23 | -------------------------------------------------------------------------------- /includes/admin/views/html-admin-setup-footer.php: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /includes/admin/views/html-admin-setup-header.php: -------------------------------------------------------------------------------- 1 | 12 |
13 |

14 | 15 |
    16 | 20 |
  • 21 |
  • 22 |
  • 23 |
24 | -------------------------------------------------------------------------------- /includes/admin/views/html-admin-setup-opt-in-usage-tracking.php: -------------------------------------------------------------------------------- 1 | 12 |

13 | 26 |

27 | -------------------------------------------------------------------------------- /includes/admin/views/html-admin-setup-step-1.php: -------------------------------------------------------------------------------- 1 | 12 |

13 | 14 |

WP Job Manager! Let\'s get your site ready to accept job listings.', 'wp-job-manager' ) ); ?>

15 |

16 |

17 | documentation will walk you through each step.', 'wp-job-manager' ), 'https://wpjobmanager.com/documentation/' ) ); 20 | ?> 21 |

22 | 23 |
24 | 25 | 26 | maybe_output_opt_in_checkbox(); ?> 27 | 28 |

29 | 30 | 31 | 32 | 33 |

34 |
35 | -------------------------------------------------------------------------------- /includes/class-wp-job-manager-blocks.php: -------------------------------------------------------------------------------- 1 | 'parent', 35 | 'id' => 'term_id', 36 | 'slug' => 'slug', 37 | ]; 38 | 39 | /** 40 | * Start the list walker. 41 | * 42 | * @see Walker::start_el() 43 | * @since 2.1.0 44 | * 45 | * @param string $output Passed by reference. Used to append additional content. 46 | * @param object $object Category data object. 47 | * @param int $depth Depth of category in reference to parents. 48 | * @param array $args 49 | * @param int $current_object_id 50 | */ 51 | public function start_el( &$output, $object, $depth = 0, $args = [], $current_object_id = 0 ) { 52 | 53 | if ( ! empty( $args['hierarchical'] ) ) { 54 | $pad = str_repeat( ' ', $depth * 3 ); 55 | } else { 56 | $pad = ''; 57 | } 58 | 59 | $cat_name = apply_filters( 'list_product_cats', $object->name, $object ); 60 | 61 | $value = isset( $args['value'] ) && 'id' === $args['value'] ? $object->term_id : $object->slug; 62 | 63 | $output .= "\t\n"; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /includes/class-wp-job-manager-data-exporter.php: -------------------------------------------------------------------------------- 1 | __( 'WP Job Manager', 'wp-job-manager' ), 28 | 'callback' => [ __CLASS__, 'user_data_exporter' ], 29 | ]; 30 | return $exporters; 31 | } 32 | 33 | /** 34 | * Data exporter 35 | * 36 | * @param string $email_address User email address. 37 | * @return array 38 | */ 39 | public static function user_data_exporter( $email_address ) { 40 | $export_items = []; 41 | $user = get_user_by( 'email', $email_address ); 42 | if ( false === $user ) { 43 | return [ 44 | 'data' => $export_items, 45 | 'done' => true, 46 | ]; 47 | } 48 | 49 | $user_data_to_export = []; 50 | $user_meta_keys = [ 51 | '_company_logo' => __( 'Company Logo', 'wp-job-manager' ), 52 | '_company_name' => __( 'Company Name', 'wp-job-manager' ), 53 | '_company_website' => __( 'Company Website', 'wp-job-manager' ), 54 | '_company_tagline' => __( 'Company Tagline', 'wp-job-manager' ), 55 | '_company_twitter' => __( 'Company Twitter', 'wp-job-manager' ), 56 | '_company_video' => __( 'Company Video', 'wp-job-manager' ), 57 | ]; 58 | 59 | foreach ( $user_meta_keys as $user_meta_key => $name ) { 60 | $user_meta = get_user_meta( $user->ID, $user_meta_key, true ); 61 | 62 | if ( empty( $user_meta ) ) { 63 | continue; 64 | } 65 | 66 | if ( '_company_logo' === $user_meta_key ) { 67 | $user_meta = wp_get_attachment_url( $user_meta ); 68 | if ( false === $user_meta ) { 69 | continue; 70 | } 71 | } 72 | 73 | $user_data_to_export[] = [ 74 | 'name' => $name, 75 | 'value' => $user_meta, 76 | ]; 77 | } 78 | 79 | $export_items[] = [ 80 | 'group_id' => 'wpjm-user-data', 81 | 'group_label' => __( 'WP Job Manager User Data', 'wp-job-manager' ), 82 | 'item_id' => "wpjm-user-data-{$user->ID}", 83 | 'data' => $user_data_to_export, 84 | ]; 85 | 86 | return [ 87 | 'data' => $export_items, 88 | 'done' => true, 89 | ]; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /includes/class-wp-job-manager-rest-api.php: -------------------------------------------------------------------------------- 1 | get_data(); 38 | 39 | if ( ! isset( $data['meta'] ) ) { 40 | $data['meta'] = []; 41 | } 42 | 43 | foreach ( $data['meta'] as $meta_key => $meta_value ) { 44 | if ( isset( $fields[ $meta_key ] ) && is_callable( $fields[ $meta_key ]['auth_view_callback'] ) ) { 45 | $is_viewable = call_user_func( $fields[ $meta_key ]['auth_view_callback'], false, $meta_key, $post->ID, $current_user->ID ); 46 | if ( ! $is_viewable ) { 47 | unset( $data['meta'][ $meta_key ] ); 48 | } 49 | } 50 | } 51 | 52 | $response->set_data( $data ); 53 | 54 | return $response; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /includes/emails/class-wp-job-manager-email-admin-expiring-job.php: -------------------------------------------------------------------------------- 1 | array( 40 | 'Version' => '1.0.0', 41 | ), 42 | 'jetpack/jetpack.php' => array( 43 | 'Version' => '1.1.1', 44 | ), 45 | 'test-dev/test.php' => array( 46 | 'Version' => '1.1.1', 47 | ), 48 | 'test/test.php' => array( 49 | 'Version' => '1.0.0', 50 | ), 51 | 'my-favorite-plugin/my-favorite-plugin.php' => array( 52 | 'Version' => '1.0.0', 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/usage-tracking/tests/support/wp-die-exception.php: -------------------------------------------------------------------------------- 1 | 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 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wp-job-manager", 3 | "version": "2.4.0", 4 | "description": "WP Job Manager", 5 | "author": "Automattic", 6 | "license": "GPL-2.0-or-later", 7 | "keywords": [ 8 | "wordpress-plugin" 9 | ], 10 | "homepage": "http://wordpress.org/plugins/wp-job-manager/", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/Automattic/WP-Job-Manager.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/Automattic/WP-Job-Manager/issues" 17 | }, 18 | "engines": { 19 | "npm": ">=7.0.0" 20 | }, 21 | "dependencies": { 22 | "select2": "4.0.13" 23 | }, 24 | "devDependencies": { 25 | "@wordpress/jest-preset-default": "11.1.0", 26 | "@wordpress/scripts": "26.1.0", 27 | "chalk": "^5.3.0", 28 | "dotenv": "^16.3.1", 29 | "file-loader": "6.2.0", 30 | "grunt": "^1.5.3", 31 | "husky": "7.0.4", 32 | "inquirer": "^9.2.10", 33 | "lint-staged": "12.3.4", 34 | "zx": "^7.2.3" 35 | }, 36 | "scripts": { 37 | "build": "./scripts/build-plugin.sh", 38 | "build:assets": "wp-scripts build", 39 | "build:vendor": "cp ./node_modules/select2/dist/css/select2.min.css ./node_modules/select2/dist/js/select2.full.min.js ./assets/lib/select2", 40 | "postarchive": "rm -rf $npm_package_name && unzip $npm_package_name.zip -d $npm_package_name && rm $npm_package_name.zip && zip -r $npm_package_name.zip $npm_package_name && rm -rf $npm_package_name", 41 | "check-engines": "wp-scripts check-engines", 42 | "check-licenses": "wp-scripts check-licenses", 43 | "format:js": "wp-scripts format-js", 44 | "lint:css": "wp-scripts lint-style assets/css", 45 | "lint:js": "wp-scripts lint-js assets/js", 46 | "lint:php": "./vendor/bin/phpcs", 47 | "lint:pkg-json": "wp-scripts lint-pkg-json", 48 | "packages-update": "wp-scripts packages-update", 49 | "prepare": "husky install", 50 | "start": "wp-scripts start", 51 | "test": "npm run test-php", 52 | "test-php": "./vendor/bin/phpunit", 53 | "i18n:build": "npm run i18n:php", 54 | "i18n:php": "wp i18n make-pot --exclude=lib,build,vendor,node_modules --skip-js --headers='{\"Last-Translator\":null,\"Language-Team\":null,\"Report-Msgid-Bugs-To\":\"https://wordpress.org/support/plugin/wp-job-manager/\"}' . languages/wp-job-manager.pot", 55 | "release": "./scripts/prepare-release.sh" 56 | }, 57 | "lint-staged": { 58 | "*.php": [ 59 | "php -l -d display_errors=0", 60 | "./vendor/bin/phpcs --standard=phpcs.xml.dist --colors --encoding=utf-8 -p" 61 | ] 62 | }, 63 | "config": { 64 | "wp_org_slug": "wp-job-manager" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | tests/php/tests 21 | lib/usage-tracking/tests 22 | 23 | 24 | 25 | 26 | . 27 | 28 | tests 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # WP Job Manager 2 | 3 | ## Description ## 4 | 5 | WP Job Manager is a **lightweight** job listing plugin for adding job-board like functionality to your WordPress site. Being shortcode based, it can work with any theme (given a bit of CSS styling) and is really simple to setup. 6 | 7 | ### Documentation ### 8 | 9 | Documentation for the core plugin and add-ons can be found [on the docs site here](https://wpjobmanager.com/documentation/). Please take a look before requesting support because it covers all frequently asked questions! 10 | 11 | ### Contributing and reporting bugs ### 12 | 13 | You can contribute code to this plugin via GitHub: [https://github.com/Automattic/WP-Job-Manager](https://github.com/Automattic/WP-Job-Manager) and localizations via [https://translate.wordpress.org/projects/wp-plugins/wp-job-manager](https://translate.wordpress.org/projects/wp-plugins/wp-job-manager) 14 | 15 | Thanks to all of our contributors. 16 | 17 | ### Support ### 18 | 19 | Use the WordPress.org forums for community support where we try to help all users. If you spot a bug, you can log it (or fix it) on [Github](https://github.com/Automattic/WP-Job-Manager) where we can act upon them more efficiently. 20 | 21 | If you need help with one of our add-ons, [please raise a ticket in our help desk](https://wpjobmanager.com/support/). 22 | 23 | If you want help with a customization, please consider hiring a developer! [http://jobs.wordpress.net/](http://jobs.wordpress.net/) is a good place to start. 24 | 25 | ## Installation ## 26 | 27 | ### Automatic installation ### 28 | 29 | Automatic installation is the easiest option as WordPress handles the file transfers itself and you don't even need to leave your web browser. To do an automatic install, log in to your WordPress admin panel, navigate to the Plugins menu and click Add New. 30 | 31 | In the search field type "WP Job Manager" and click Search Plugins. Once you've found the plugin you can view details about it such as the point release, rating and description. Most importantly of course, you can install it by clicking _Install Now_. 32 | 33 | ### Manual installation ### 34 | 35 | The manual installation method involves downloading the plugin and uploading it to your web server via your favorite FTP application. 36 | 37 | * Download the plugin file to your computer and unzip it 38 | * Using an FTP program, or your hosting control panel, upload the unzipped plugin folder to your WordPress installation's `wp-content/plugins/` directory. 39 | * Activate the plugin from the Plugins menu within the WordPress admin. 40 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "labels": ["[Type] Maintenance"], 6 | "packageRules": [ 7 | { 8 | "depTypeList": ["devDependencies"], 9 | "extends": ["schedule:monthly"], 10 | "groupName": "devDependencies" 11 | }, 12 | { 13 | "depTypeList": ["dependencies"], 14 | "extends": ["schedule:monthly"] 15 | }, 16 | { 17 | "depTypeList": ["require-dev"], 18 | "extends": ["schedule:monthly"], 19 | "groupName": "require-dev" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /scripts/RELEASE_PR_TEMPLATE.md.mjs: -------------------------------------------------------------------------------- 1 | export default ( { changelog, version }) => ` 2 | 3 | > [!IMPORTANT] 4 | > Merging this PR will build and publish the new version automatically as a GitHub release, then deploy the new version on the WordPress.org plugin repository. 5 | > 6 | 7 | ## WP Job Manager ${version} 8 | 9 | > [!NOTE] 10 | > These release notes between the two \`---\` lines will be the final changelog entry for the release. Edit them freely here before merging. 11 | > 12 | 13 | ### Release Notes 14 | 15 | --- 16 | ${ changelog } 17 | --- 18 | 19 | ### Release 20 | 21 | > [!NOTE] 22 | > Click 'Ready for Review', ping the team, review the PR and merge. Upon merging, automation will: 23 | > - Write the release notes above to the changelog 24 | > - Create and tag a new GitHub release 25 | > - Deploy the release to WordPress.org 26 | `; 27 | -------------------------------------------------------------------------------- /scripts/build-plugin.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Step 1: Create a new folder called `build` 4 | mkdir -p build/wp-job-manager 5 | 6 | # Step 2: Copy the current folder to the build folder, excluding undesired directories 7 | rsync -av --progress . build/wp-job-manager --exclude build --exclude node_modules --exclude vendor --exclude .git --exclude .github --exclude .psalm --exclude tests --exclude .husky --exclude docs 8 | 9 | # Navigate to build directory 10 | cd build/wp-job-manager 11 | 12 | # Step 3: Build assets 13 | npm run build:assets 14 | 15 | # Step 4: Run composer install without development dependencies 16 | composer install --no-dev 17 | 18 | # Step 5: Zip the entire contents of the build folder into `wp-job-manager.zip` excluding the files from the exclude.lst file 19 | # Navigate one level up, so the zip command includes the build directory content 20 | cd .. 21 | zip -r wp-job-manager.zip wp-job-manager -x@../scripts/exclude.lst 22 | 23 | # Step 6: Remove the contents of the build folder except the new zip file 24 | rm -rf wp-job-manager 25 | 26 | echo "Build process complete. The wp-job-manager.zip file is ready in the /build folder." 27 | -------------------------------------------------------------------------------- /scripts/exclude.lst: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | wp-job-manager/.babelrc 3 | wp-job-manager/.editorconfig 4 | wp-job-manager/.eslintignore 5 | wp-job-manager/.eslintrc.js 6 | wp-job-manager/.git-blame-ignore-revs 7 | wp-job-manager/.gitignore 8 | wp-job-manager/.jshintrc 9 | wp-job-manager/.prettierrc 10 | wp-job-manager/bin/* 11 | wp-job-manager/build/* 12 | wp-job-manager/composer.* 13 | wp-job-manager/docs/* 14 | wp-job-manager/jest.config.js 15 | wp-job-manager/node_modules/* 16 | wp-job-manager/package-lock.json 17 | wp-job-manager/package.json 18 | wp-job-manager/*.dist 19 | wp-job-manager/psalm.xml 20 | wp-job-manager/scripts/* 21 | wp-job-manager/tests/* 22 | wp-job-manager/.git/* 23 | wp-job-manager/.github/* 24 | wp-job-manager/readme.md 25 | wp-job-manager/assets/dist/css/*.js 26 | wp-job-manager/assets/css/* 27 | wp-job-manager/assets/font/* 28 | wp-job-manager/assets/js/* 29 | wp-job-manager/renovate.json 30 | wp-job-manager/webpack.config.js 31 | -------------------------------------------------------------------------------- /scripts/includes/chalk-lite.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Test if color is supported. 4 | function color_supported { 5 | [[ -n "$NO_COLOR" ]] && return 1 6 | [[ -n "$FORCE_COLOR" ]] && return 0 7 | [[ -t 1 ]] 8 | } 9 | 10 | # Print possibly-colored text to standard output. 11 | # 12 | # The first parameter is the ANSI SGR parameter string, i.e. the part that 13 | # goes in between the `\e[` and the `m`. 14 | # 15 | # If passed additional parameters, those are printed. Otherwise, lines are read 16 | # from standard input and printed. Examples: 17 | # 18 | # chalk 31 'This prints red text' 19 | # 20 | # chalk 31 <&2 25 | function chalk { 26 | local CC="$1" LINE FMT 27 | shift 28 | if color_supported; then 29 | FMT="\e[${CC}m%s\e[0m\n" 30 | else 31 | FMT="%s\n" 32 | fi 33 | if [[ $# -gt 0 ]]; then 34 | printf "$FMT" "$*" 35 | else 36 | while IFS= read -r LINE; do 37 | printf "$FMT" "$LINE" 38 | done 39 | fi 40 | } 41 | 42 | # All the rest of these functions are just wrappers around `chalk` that 43 | # supply a particular first argument. `error` and `warn` also output to STDERR 44 | # by default. 45 | # 46 | # info "Here's some information" 47 | # 48 | # error <<-EOM 49 | # Something went wrong! 50 | # EOM 51 | 52 | 53 | function debug { 54 | chalk '1;30' "$@" 55 | } 56 | 57 | function success { 58 | if [[ $(tput colors 2>/dev/null) -gt 8 ]]; then 59 | chalk '1;38;5;41' "$@" 60 | else 61 | chalk '1;32' "$@" 62 | fi 63 | } 64 | 65 | function info { 66 | chalk '1;37' "$@" 67 | } 68 | 69 | function warn { 70 | chalk '1;33' "$@" >&2 71 | } 72 | 73 | function error { 74 | chalk '1;31' "$@" >&2 75 | } 76 | 77 | function die { 78 | error "$@" 79 | exit 1 80 | } 81 | 82 | function prompt { 83 | yellow "$@" 84 | } 85 | 86 | function red { 87 | chalk '31' "$@" 88 | } 89 | 90 | function green { 91 | chalk '32' "$@" 92 | } 93 | 94 | function jetpackGreen { 95 | if [[ $(tput colors 2>/dev/null) -gt 8 ]]; then 96 | chalk '38;5;41' "$@" 97 | else 98 | chalk '32' "$@" 99 | fi 100 | } 101 | 102 | function yellow { 103 | chalk '33' "$@" 104 | } 105 | 106 | function blue { 107 | chalk '34' "$@" 108 | } 109 | 110 | function purple { 111 | chalk '35' "$@" 112 | } 113 | 114 | function cyan { 115 | chalk '36' "$@" 116 | } 117 | -------------------------------------------------------------------------------- /scripts/includes/proceed_p.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | INTERACTIVE=true 4 | if [[ ! -t 0 ]]; then 5 | INTERACTIVE=false 6 | fi 7 | 8 | . "$(dirname "$BASH_SOURCE[0]")/chalk-lite.sh" 9 | 10 | # Ask whether to proceed. 11 | # 12 | # Args: 13 | # 1: The situation that requires prompting. 14 | # 2: Optional text to replace "Proceed?" as the question. 15 | # 16 | # Returns success if "yes", failure if "no" or non-interactive. 17 | function proceed_p { 18 | if ! $INTERACTIVE; then 19 | error "$1 Aborting" 20 | return 42 21 | fi 22 | local OK 23 | 24 | # Clear input before prompting 25 | while read -r -t 0 OK; do read -r OK; done 26 | 27 | local PROMPT 28 | [[ -n "$1" ]] && PROMPT="$1 " 29 | PROMPT="${PROMPT}${2:-Proceed?} [y/N] " 30 | if color_supported; then 31 | PROMPT=$(FORCE_COLOR=1 prompt "$PROMPT") 32 | fi 33 | 34 | read -n 1 -p "$PROMPT" OK 35 | echo "" 36 | [[ "$OK" == "y" || "$OK" == "Y" ]] 37 | } 38 | -------------------------------------------------------------------------------- /scripts/prepare-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | if [ -z "$1" ]; then 5 | echo "Prepare a new version for release." 6 | echo "" 7 | echo "Usage: npm run release [version]" 8 | exit 1 9 | fi 10 | 11 | node scripts/prepare-release.mjs wp-job-manager $1 12 | -------------------------------------------------------------------------------- /templates/access-denied-browse-job_listings.php: -------------------------------------------------------------------------------- 1 | 19 |

20 | -------------------------------------------------------------------------------- /templates/access-denied-single-job_listing.php: -------------------------------------------------------------------------------- 1 | 19 | 20 |

21 | -------------------------------------------------------------------------------- /templates/content-job_listing.php: -------------------------------------------------------------------------------- 1 | 21 |
  • data-longitude="geolocation_long ); ?>" data-latitude="geolocation_lat ); ?>"> 22 | 23 | 24 |
    25 |

    26 |
    27 | ', ' ' ); ?> 28 | ', '' ); ?> 29 |
    30 |
    31 |
    32 | 33 |
    34 |
      35 | 36 | 37 | 38 | 39 | 40 |
    • name ); ?>
    • 41 | 42 | 43 | 44 |
    • 45 | 46 | 47 |
    48 |
    49 |
  • 50 | -------------------------------------------------------------------------------- /templates/content-no-jobs-found.php: -------------------------------------------------------------------------------- 1 | 19 | 20 |
  • 21 | 22 |

    23 | 24 | -------------------------------------------------------------------------------- /templates/content-single-job_listing-company.php: -------------------------------------------------------------------------------- 1 | 25 |
    26 | 27 | 28 |
    29 |

    30 | 31 | 32 | 33 | 34 | ', '' ); ?> 35 |

    36 | ', '

    ' ); ?> 37 |
    38 | 39 | 40 |
    41 | -------------------------------------------------------------------------------- /templates/content-single-job_listing-meta.php: -------------------------------------------------------------------------------- 1 | 24 | 25 |
      26 | 27 | 28 | 29 | 30 | 31 | 32 |
    • name ); ?>
    • 33 | 34 | 35 | 36 | 37 |
    • 38 | 39 |
    • 40 | 41 | 44 |
    • 45 | 48 | 49 | 50 |
    • 51 | post_status ) : ?> 52 |
    • 53 | 54 | 55 | 56 |
    57 | 58 | 59 | -------------------------------------------------------------------------------- /templates/content-single-job_listing.php: -------------------------------------------------------------------------------- 1 | ID ) ) : ?> 22 |
    23 | post_status ) : ?> 24 |
    25 | 26 | 35 | 36 |
    37 | 38 |
    39 | 40 | 41 | 42 | 43 | 44 | 50 | 51 |
    52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /templates/content-summary-job_listing.php: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
    name ); ?>
    27 | 28 | 29 | 30 | 31 | 32 | <?php the_company_name(); ?> 33 | 34 | 35 |
    36 | 37 |

    38 | 39 |

    40 | 41 |
    42 |
    43 | -------------------------------------------------------------------------------- /templates/content-widget-job_listing.php: -------------------------------------------------------------------------------- 1 | 18 |
  • > 19 | 20 | 21 |
    22 | 23 |
    24 | 25 |
    26 |
    27 |

    28 |
    29 |
      30 |
    • 31 |
    • 32 | 33 | 34 | 35 |
    • name ); ?>
    • 36 | 37 | 38 |
    39 |
    40 |
    41 |
  • 42 | -------------------------------------------------------------------------------- /templates/content-widget-no-jobs-found.php: -------------------------------------------------------------------------------- 1 | ID ) ); 28 | 29 | echo '

    '; 30 | if ( $expiring_today ) { 31 | // translators: %1$s placeholder is URL to the blog. %2$s placeholder is the name of the site. 32 | echo wp_kses_post( sprintf( __( 'The following job listing is expiring today from %2$s.', 'wp-job-manager' ), esc_url( home_url() ), esc_html( get_bloginfo( 'name' ) ) ) ); 33 | } else { 34 | // translators: %1$s placeholder is URL to the blog. %2$s placeholder is the name of the site. 35 | echo wp_kses_post( sprintf( __( 'The following job listing is expiring soon from %2$s.', 'wp-job-manager' ), esc_url( home_url() ), esc_html( get_bloginfo( 'name' ) ) ) ); 36 | } 37 | 38 | echo ' '; 39 | 40 | // translators: Placeholder is URL to site's WP admin. 41 | echo wp_kses_post( sprintf( __( 'Visit WordPress admin to manage the listing.', 'wp-job-manager' ), esc_url( $edit_post_link ) ) ); 42 | echo '

    '; 43 | 44 | /** 45 | * Show details about the job listing. 46 | * 47 | * @param WP_Post $job The job listing to show details for. 48 | * @param WP_Job_Manager_Email $email Email object for the notification. 49 | * @param bool $sent_to_admin True if this is being sent to an administrator. 50 | * @param bool $plain_text True if the email is being sent as plain text. 51 | */ 52 | do_action( 'job_manager_email_job_details', $job, $email, true, $plain_text ); 53 | -------------------------------------------------------------------------------- /templates/emails/admin-new-job.php: -------------------------------------------------------------------------------- 1 | 23 |

    %s.', 'wp-job-manager' ), 28 | home_url(), 29 | get_bloginfo( 'name' ) 30 | ) 31 | ); 32 | switch ( $job->post_status ) { 33 | case 'publish': 34 | printf( ' ' . esc_html__( 'It has been published and is now available to the public.', 'wp-job-manager' ) ); 35 | break; 36 | case 'pending': 37 | echo wp_kses_post( 38 | sprintf( 39 | // translators: Placeholder %s is the admin job listings URL. 40 | ' ' . __( 'It is awaiting approval by an administrator in WordPress admin.','wp-job-manager' ), 41 | esc_url( admin_url( 'edit.php?post_type=job_listing' ) ) 42 | ) 43 | ); 44 | break; 45 | } 46 | ?>

    47 | 23 |

    %s.', 'wp-job-manager' ), home_url(), esc_html( get_bloginfo( 'name' ) ) ) ); 27 | switch ( $job->post_status ) { 28 | case 'publish': 29 | printf( ' ' . esc_html__( 'The changes have been published and are now available to the public.', 'wp-job-manager' ) ); 30 | break; 31 | case 'pending': 32 | echo wp_kses_post( sprintf( 33 | // translators: Placeholder %s is the admin job listings URL. 34 | ' ' . __( 'The job listing is not publicly available until the changes are approved by an administrator in the site\'s WordPress admin.', 'wp-job-manager' ), 35 | esc_url( admin_url( 'edit.php?post_type=job_listing' ) ) 36 | ) ); 37 | break; 38 | } 39 | ?>

    40 | 18 | 19 |
    20 | 21 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /templates/emails/email-header.php: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | > 21 | 22 | 23 | <?php echo esc_html( get_bloginfo( 'name' ) ); ?> 24 | 25 | 26 |