├── .csslintrc ├── .distignore ├── .eslintignore ├── .eslintrc ├── .github ├── ISSUE_TEMPLATE │ ├── Bug_report.md │ ├── Bug_report.yml │ ├── Feature_request.md │ └── Feature_request.yml ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── labeler.yml └── workflows │ ├── build-dev-artifacts.yml │ ├── create-tag.yml │ ├── deploy-wporg.yml │ ├── issue-labeler.yml │ ├── new-issues.yml │ ├── pr-announcer-docs.yml │ ├── pr-checklist.yml │ ├── pr_verify_linked_issue.yml │ ├── sync-branches.yml │ ├── sync-wporg-assets.yml │ ├── test-e2e.yml │ ├── test-lint-js.yml │ └── test-php.yml ├── .gitignore ├── .nvmrc ├── .releaserc.yml ├── .wordpress-org ├── banner-1544x500-de_DE.png ├── banner-1544x500-de_DE_formal.png ├── banner-1544x500.png ├── banner-722x250-de_DE.png ├── banner-722x250-de_DE_formal.png ├── banner-772x250.png ├── icon-128x128.gif ├── icon-256x256.gif ├── screenshot-1.gif ├── screenshot-2.gif ├── screenshot-3.gif ├── screenshot-4.gif ├── screenshot-5.gif ├── screenshot-6.jpg └── screenshot-7.jpg ├── .wp-env.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE.txt ├── bin ├── dist.sh ├── generatePlaygroundPreviewLink.js ├── install-wp-tests.sh └── update-changelog.js ├── composer.json ├── composer.lock ├── css ├── feedzy-elementor-widget.css ├── feedzy-rss-feed-import.css ├── feedzy-rss-feeds.css ├── form.css ├── metabox-settings.css ├── settings.css └── smart_wizard_all.min.css ├── feedzy-rss-feed.php ├── form └── form.php ├── img ├── Protect-your-Brand.jpg ├── Unlimited-Content.jpg ├── World-class-support.jpg ├── alternate-file.svg ├── boost-logo.svg ├── copy.svg ├── dark-feedzy-logo.png ├── dark-mode-default.png ├── dark-mode-style1.png ├── dark-mode-style2.png ├── el-lock.png ├── external-link.png ├── features-affiliate-ready.jpg ├── features-caching.jpg ├── features-feed-to-post.jpg ├── features-templates.jpg ├── features-widgets-support.jpg ├── feed-to-post.jpg ├── feedzy-default-template.png ├── feedzy-demo.jpg ├── feedzy-logo.png ├── feedzy-rss-feeds-wordai.jpg ├── feedzy-style1-template.png ├── feedzy-style2-template.png ├── feedzy.png ├── feedzy.svg ├── finish-feedback.svg ├── improve-feedzy.png ├── in-feedzy.jpg ├── light-feedzy-logo.png ├── light-mode-default.png ├── light-mode-style1.png ├── light-mode-style2.png ├── mask-loader.jpg ├── newsletter-img.png ├── rephrase-feeds-content.jpg ├── shortcode.jpg ├── spin_light.gif └── validate-RSS-feed.jpg ├── includes ├── abstract │ └── feedzy-rss-feeds-admin-abstract.php ├── admin │ ├── feedzy-rss-feeds-actions.php │ ├── feedzy-rss-feeds-admin.php │ ├── feedzy-rss-feeds-import.php │ ├── feedzy-rss-feeds-options.php │ ├── feedzy-rss-feeds-ui-lang.php │ ├── feedzy-rss-feeds-ui.php │ ├── feedzy-rss-feeds-upgrader.php │ └── feedzy-wp-widget.php ├── elementor │ ├── controls │ │ ├── datetime-local.php │ │ └── template-layout.php │ ├── feedzy-rss-feeds-elementor.php │ └── widgets │ │ └── register-widget.php ├── feedzy-rss-feeds-activator.php ├── feedzy-rss-feeds-deactivator.php ├── feedzy-rss-feeds-feed-tweaks.php ├── feedzy-rss-feeds-limited-offers.php ├── feedzy-rss-feeds-loader.php ├── feedzy-rss-feeds.php ├── gutenberg │ ├── feedzy-rss-feeds-gutenberg-block.php │ └── feedzy-rss-feeds-loop-block.php ├── layouts │ ├── feedzy-documentation.php │ ├── feedzy-improve.php │ ├── feedzy-pro.php │ ├── feedzy-support.php │ ├── feedzy-tutorial.php │ ├── header.php │ ├── integration.php │ ├── settings.php │ └── setup-wizard.php ├── util │ ├── feedzy-rss-feeds-conditions.php │ ├── feedzy-rss-feeds-util-scheduler.php │ └── feedzy-rss-feeds-util-simplepie.php └── views │ ├── amazon-product-advertising-view.php │ ├── css │ ├── chosen-sprite.png │ ├── chosen-sprite@2x.png │ ├── chosen.css │ ├── import-metabox-edit.css │ ├── style-wizard.css │ └── tagify.css │ ├── import-metabox-edit.php │ ├── js │ ├── chosen.js │ ├── import-metabox-edit.js │ └── jquery.tagify.min.js │ ├── misc-view.php │ ├── openai-view.php │ ├── openrouter-view.php │ ├── spinnerchief-view.php │ └── wordai-view.php ├── index.php ├── js ├── ActionPopup │ ├── Actions.js │ ├── SortableItem.js │ └── index.js ├── Conditions │ ├── ConditionsControl.js │ ├── DateTimeControl.js │ ├── PanelTab.js │ └── index.js ├── FeedBack │ ├── feedback-form.js │ └── index.js ├── FeedzyBlock │ ├── Editor.js │ ├── attributes.js │ ├── index.js │ ├── inspector.js │ ├── radio-image-control │ │ ├── index.js │ │ └── style.scss │ ├── style.scss │ └── utils.js ├── FeedzyLoop │ ├── block.json │ ├── components │ │ ├── FeedControl.js │ │ └── PatternSelector.js │ ├── controls.js │ ├── edit.js │ ├── editor.scss │ ├── extension.js │ ├── index.js │ ├── placeholder.js │ ├── style.scss │ └── variations.js ├── Onboarding │ └── index.js ├── Review │ └── index.js ├── categories.js ├── feedzy-elementor-widget.js ├── feedzy-lazy.js ├── feedzy-rss-feeds-ui-mce.js ├── feedzy-setting.js ├── feedzy-setup-wizard.js ├── jquery.smartWizard.min.js └── telemetry.js ├── package-lock.json ├── package.json ├── phpcs.xml ├── phpunit.xml ├── readme.md ├── readme.txt ├── tests ├── bootstrap.php ├── e2e │ ├── config │ │ ├── flaky-tests-reporter.js │ │ └── global-setup.js │ ├── playwright.config.js │ ├── specs │ │ ├── feed.spec.js │ │ ├── import.spec.js │ │ ├── loop.spec.js │ │ └── upsell.spec.js │ └── utils.js ├── test-conditions.php ├── test-image-import.php ├── test-import.php ├── test-plugin.php └── test-post-access.php └── uninstall.php /.csslintrc: -------------------------------------------------------------------------------- 1 | --exclude-exts=.min.css 2 | --ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes 3 | -------------------------------------------------------------------------------- /.distignore: -------------------------------------------------------------------------------- 1 | .git 2 | .distignore 3 | .gitignore 4 | .travis.yml 5 | Gruntfile.js 6 | grunt 7 | phpcs.xml 8 | node_modules 9 | logs 10 | package.json 11 | bin 12 | tests 13 | phpunit.xml 14 | npm-debug.log 15 | artifact 16 | composer.json 17 | composer.lock 18 | package-lock.json 19 | key.enc 20 | js/FeedzyBlock 21 | js/Conditions/ 22 | js/Onboarding/ 23 | js/ActionPopup/ 24 | js/FeedBack/ 25 | js/Review/ 26 | webpack.config.js 27 | dist 28 | cypress 29 | cypress.json 30 | cypress.env.json.template 31 | docker-compose.travis.yml 32 | .github 33 | .idea 34 | .codeclimate.yml 35 | .csslintrc 36 | .eslintignore 37 | .eslintrc 38 | .jshintignore 39 | .wordpress-org 40 | .nvmrc 41 | .releaserc.yml 42 | docker-compose.ci.yml 43 | .wp-env.json 44 | artifacts 45 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*{.,-}min.js 2 | includes/gutenberg/dist/*.js 3 | webpack.config.js 4 | Gruntfile.js 5 | .eslintrc -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true 5 | }, 6 | "extends": "plugin:@wordpress/eslint-plugin/recommended", 7 | "parserOptions": { 8 | "ecmaFeatures": { 9 | "jsx": true 10 | }, 11 | "ecmaVersion": "latest" 12 | }, 13 | "ignorePatterns": ["**/vendor/**", "**/build/**", "**/*.min.js", "**/node_modules/**"], 14 | "rules": { 15 | "@wordpress/i18n-text-domain": ["error", { 16 | "allowedTextDomain": ["feedzy-rss-feeds"] 17 | }], 18 | "@wordpress/i18n-no-flanking-whitespace": "error", 19 | "@wordpress/data-no-store-string-literals": "error", 20 | "@wordpress/wp-global-usage": "error", 21 | "prettier/prettier": "warn", 22 | "import/no-extraneous-dependencies": "warn", 23 | "prefer-const": "warn", 24 | "dot-notation": "warn", 25 | "react/jsx-key": "warn", 26 | "jsx-a11y/no-static-element-interactions": "warn", 27 | "no-undef": "warn", 28 | "object-shorthand": "warn", 29 | "no-else-return": "warn", 30 | "jsx-a11y/click-events-have-key-events": "warn", 31 | "jsx-a11y/anchor-is-valid": "warn", 32 | "camelcase": "warn", 33 | "jsdoc/check-line-alignment": "warn", 34 | "jsdoc/check-alignment": "warn", 35 | "jsx-a11y/label-has-associated-control": "warn", 36 | "react/no-unknown-property": "warn", 37 | "@wordpress/no-unused-vars-before-return": "warn", 38 | "jsdoc/empty-tags": "warn", 39 | "no-lonely-if": "warn", 40 | "no-var": "warn", 41 | "no-unused-vars": "warn", 42 | "no-console": "warn", 43 | "jsx-a11y/alt-text": "warn", 44 | "react/no-deprecated": "warn", 45 | "jsx-a11y/no-noninteractive-element-interactions": "warn", 46 | "jsx-a11y/no-autofocus": "warn", 47 | "no-nested-ternary": "warn", 48 | "jsdoc/require-param": "warn", 49 | "jsdoc/require-returns-description": "warn", 50 | "jsdoc/require-returns-type": "warn" 51 | } 52 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug so we can get to squashing it. 4 | 5 | --- 6 | 7 | ### Description: 8 | 9 | 10 | ### How to reproduce: 11 | 12 | 1. 13 | 2. 14 | 3. 15 | 16 | ### Expected behaviour: 17 | 18 | ### Current behaviour: 19 | 20 | ### Reference: 21 | 22 | 23 | ### Technical info 24 | * WordPress version: x.x.x 25 | * Plugin version: x.x.x -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report a bug so we can get to squashing it. 3 | labels: bug 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: textarea 10 | attributes: 11 | label: Description 12 | description: Please write a brief description of the bug, including what you expect to happen and what is currently happening. 13 | placeholder: | 14 | Feature '...' is not working properly. I expect '...' to happen, but '...' happens instead 15 | validations: 16 | required: true 17 | 18 | - type: textarea 19 | attributes: 20 | label: Step-by-step reproduction instructions 21 | description: Please write the steps needed to reproduce the bug. 22 | placeholder: | 23 | 1. Go to '...' 24 | 2. Click on '...' 25 | 3. Scroll down to '...' 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | attributes: 31 | label: Screenshots, screen recording, code snippet or Help Scout ticket 32 | description: | 33 | If possible, please upload a screenshot or screen recording which demonstrates the bug. 34 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 35 | Tip: You can include links to customer Help Scout support thread. 36 | validations: 37 | required: false 38 | 39 | - type: input 40 | attributes: 41 | label: Environment info 42 | description: | 43 | Please share a https://pastebin.com/ link of your system details by going to site Admin -> Tools -> Site Health -> Info and Copy to Clipboard 44 | placeholder: pastebin.com/ ... 45 | validations: 46 | required: false 47 | 48 | - type: dropdown 49 | id: regression 50 | attributes: 51 | label: Is the issue you are reporting a regression 52 | description: | 53 | Choose "Yes" if the bug appeared after updating the product, meaning it worked before but not now. Choose "No" if the bug isn't caused by an update. Marking a bug as regression helps us fix issues from new changes faster. 54 | multiple: false 55 | options: 56 | - 'No' 57 | - 'Yes, this is a regression.' 58 | validations: 59 | required: true 60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a feature that we can implement. 4 | 5 | --- 6 | 7 | ### Description: 8 | 9 | 10 | ### Alternatives: 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest a feature that we can implement. 3 | labels: new feature 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: What problem does this address? 8 | description: | 9 | Can you give us a little more insight into this feature request? We'd love to know if it's related to any problems or pain points you've been facing. 10 | If so, can you please let us know what the issue is in a clear and simple way? 11 | Tip: If this is related to a customer request, please add the Help Scout thread URL. 12 | placeholder: | 13 | For example, something like "I find it tough when..." or "I get frustrated because..." would be great. 14 | validations: 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: What is your proposed solution? 19 | description: Can you please specify the desired feature or improvement and how it resolves the problem mentioned? 20 | validations: 21 | required: false 22 | - type: dropdown 23 | id: doc-needed 24 | attributes: 25 | label: Will this feature require documentation? (Optional) 26 | description: | 27 | Does this feature require the creation or update of documentation? If you're unsure, feel free to skip this. 28 | multiple: false 29 | options: 30 | - 'I dont know.' 31 | - 'No.' 32 | - 'Yes, it requires documentation.' 33 | validations: 34 | required: false -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Summary 2 | 3 | 4 | ### Will affect visual aspect of the product 5 | 6 | YES/NO 7 | 8 | ### Screenshots 9 | 10 | ### Test instructions 11 | 12 | 13 | - 14 | - 15 | 16 | ## Check before Pull Request is ready: 17 | 18 | * [ ] I have [written a test](CONTRIBUTING.md#writing-an-acceptance-test) and included it in this PR 19 | * [ ] I have [run all tests](CONTRIBUTING.md#run-tests) and they pass 20 | * [ ] The code passes when [running the PHP CodeSniffer](CONTRIBUTING.md#run-php-codesniffer) 21 | * [ ] Code meets [WordPress Coding Standards](CONTRIBUTING.md#coding-standards) for PHP, HTML, CSS and JS 22 | * [ ] [Security and Sanitization](CONTRIBUTING.md#security-and-sanitization) requirements have been followed 23 | * [ ] I have assigned a reviewer or two to review this PR (if you're not sure who to assign, we can do this step for you) 24 | 25 | 26 | Closes #. 27 | 28 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "composer" 4 | directory: "/" 5 | target-branch: "development" 6 | schedule: 7 | interval: "weekly" -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | regression: 2 | - '(Yes, this is a regression)' 3 | 4 | customer report: 5 | - '(helpscout|wordpress.org/support)' 6 | 7 | doc-needed: 8 | - '(Yes, it requires documentation.)' -------------------------------------------------------------------------------- /.github/workflows/build-dev-artifacts.yml: -------------------------------------------------------------------------------- 1 | name: Build artifact 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, ready_for_review] 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} 9 | cancel-in-progress: true 10 | jobs: 11 | dev-zip: 12 | name: Build ZIP and upload to s3 13 | if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name 14 | runs-on: ubuntu-latest 15 | outputs: 16 | branch-name: ${{ steps.retrieve-branch-name.outputs.branch_name }} 17 | git-sha-8: ${{ steps.retrieve-git-sha-8.outputs.sha8 }} 18 | steps: 19 | - name: Check out source files 20 | uses: actions/checkout@v4 21 | - name: Install composer deps 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} 24 | run: | 25 | composer install --no-dev --prefer-dist --no-progress 26 | - name: Create zip 27 | run: | 28 | npm ci 29 | npm run build 30 | CURRENT_VERSION=$(node -p -e "require('./package.json').version") 31 | COMMIT_HASH=$(git rev-parse --short HEAD) 32 | DEV_VERSION="${CURRENT_VERSION}-dev.${COMMIT_HASH}" 33 | npm run grunt version::${DEV_VERSION} 34 | npm run dist 35 | - name: Retrieve branch name 36 | id: retrieve-branch-name 37 | run: echo "::set-output name=branch_name::$(REF=${GITHUB_HEAD_REF:-$GITHUB_REF} && echo ${REF#refs/heads/} | sed 's/\//-/g')" 38 | - name: Retrieve git SHA-8 string 39 | id: retrieve-git-sha-8 40 | run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)" 41 | - name: Upload Latest Version to S3 42 | uses: jakejarvis/s3-sync-action@master 43 | with: 44 | args: --acl public-read --follow-symlinks --delete 45 | env: 46 | AWS_S3_BUCKET: ${{ secrets.AWS_DEV_BUCKET }} 47 | AWS_ACCESS_KEY_ID: ${{ secrets.S3_AWS_KEY_ARTIFACTS }} 48 | AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_AWS_SECRET_ARTIFACTS }} 49 | SOURCE_DIR: artifact/ 50 | DEST_DIR: ${{ github.event.pull_request.base.repo.name }}-${{ steps.retrieve-branch-name.outputs.branch_name }}-${{ steps.retrieve-git-sha-8.outputs.sha8 }}/ 51 | 52 | comment-on-pr: 53 | name: Comment on PR with links to plugin ZIPs 54 | if: ${{ github.head_ref && github.head_ref != null }} 55 | runs-on: ubuntu-latest 56 | needs: dev-zip 57 | env: 58 | CI: true 59 | GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} 60 | outputs: 61 | pr_number: ${{ steps.get-pr-number.outputs.num }} 62 | comment_body: ${{ steps.get-comment-body.outputs.body }} 63 | steps: 64 | - name: Check out source files 65 | uses: actions/checkout@v4 66 | - name: Get PR number 67 | id: get-pr-number 68 | run: echo "::set-output name=num::$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')" 69 | 70 | - name: Check if a comment was already made 71 | id: find-comment 72 | uses: peter-evans/find-comment@v2 73 | with: 74 | issue-number: ${{ steps.get-pr-number.outputs.num }} 75 | comment-author: pirate-bot 76 | token: ${{ secrets.BOT_TOKEN }} 77 | body-includes: Download [build] 78 | 79 | - name: Get comment body 80 | id: get-comment-body 81 | env: 82 | GITHUB_REPO_NAME: ${{ github.event.pull_request.base.repo.name }} 83 | DEV_ZIP_BRANCH_NAME: ${{ needs.dev-zip.outputs.branch-name }} 84 | DEV_ZIP_GIT_SHA_8: ${{ needs.dev-zip.outputs.git-sha-8 }} 85 | GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }} 86 | run: | 87 | previewLink=$(node ./bin/generatePlaygroundPreviewLink.js) 88 | 89 | body="Plugin build for ${{ github.event.pull_request.head.sha }} is ready :bellhop_bell:! 90 | - Download [build](https://verti-artifacts.s3.amazonaws.com/${{ github.event.pull_request.base.repo.name }}-${{ needs.dev-zip.outputs.branch-name }}-${{ needs.dev-zip.outputs.git-sha-8 }}/feedzy-rss-feeds.zip) 91 | 92 | > [!NOTE] 93 | > You can preview the changes in the Playground 94 | " 95 | body="${body//$'\n'/'%0A'}" 96 | echo "::set-output name=body::$body" 97 | - name: Create comment on PR with links to plugin builds 98 | if: ${{ steps.find-comment.outputs.comment-id == '' }} 99 | uses: peter-evans/create-or-update-comment@v2 100 | with: 101 | issue-number: ${{ steps.get-pr-number.outputs.num }} 102 | token: ${{ secrets.BOT_TOKEN }} 103 | body: ${{ steps.get-comment-body.outputs.body }} 104 | 105 | - name: Update comment on PR with links to plugin builds 106 | if: ${{ steps.find-comment.outputs.comment-id != '' }} 107 | uses: peter-evans/create-or-update-comment@v2 108 | with: 109 | comment-id: ${{ steps.find-comment.outputs.comment-id }} 110 | token: ${{ secrets.BOT_TOKEN }} 111 | edit-mode: replace 112 | body: ${{ steps.get-comment-body.outputs.body }} 113 | -------------------------------------------------------------------------------- /.github/workflows/create-tag.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | jobs: 8 | create_tag: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@master 12 | with: 13 | persist-credentials: false 14 | - name: Set up Node version 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: 18 18 | - name: Install dependencies 19 | run: npm ci 20 | - name: Release new version 21 | id: release 22 | run: | 23 | npm run release 24 | env: 25 | CI: true 26 | GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} 27 | GIT_AUTHOR_NAME: themeisle[bot] 28 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_ANNOUNCEMENTS }} 29 | SEMANTIC_RELEASE_PACKAGE: Feedzy(free) 30 | GIT_AUTHOR_EMAIL: ${{ secrets.BOT_EMAIL }} 31 | GIT_COMMITTER_NAME: themeisle[bot] 32 | GIT_COMMITTER_EMAIL: ${{ secrets.BOT_EMAIL }} 33 | -------------------------------------------------------------------------------- /.github/workflows/deploy-wporg.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | tags: 5 | - "*" 6 | jobs: 7 | tag: 8 | name: New version 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@master 12 | - name: Get the version 13 | id: get_version 14 | run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} 15 | - name: Build 16 | run: | 17 | npm ci 18 | npm run build 19 | composer install --no-dev --prefer-dist --no-progress --no-suggest 20 | - name: WordPress Plugin Deploy 21 | uses: 10up/action-wordpress-plugin-deploy@master 22 | env: 23 | SVN_PASSWORD: ${{ secrets.SVN_THEMEISLE_PASSWORD }} 24 | SVN_USERNAME: ${{ secrets.SVN_THEMEISLE_USERNAME }} 25 | - name: Send update to the store 26 | env: 27 | PRODUCT_ID: ${{ secrets.THEMEISLE_ID }} 28 | AUTH_TOKEN: ${{ secrets.THEMEISLE_STORE_AUTH }} 29 | STORE_URL: ${{ secrets.THEMEISLE_STORE_URL }} 30 | GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} 31 | BUILD_VERSION: ${{ steps.get_version.outputs.VERSION }} 32 | uses: Codeinwp/action-store-release@main 33 | -------------------------------------------------------------------------------- /.github/workflows/issue-labeler.yml: -------------------------------------------------------------------------------- 1 | name: "Issue Labeler" 2 | on: 3 | issues: 4 | types: [opened] 5 | 6 | jobs: 7 | triage: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: github/issue-labeler@master 11 | with: 12 | repo-token: "${{ secrets.BOT_TOKEN }}" 13 | enable-versioned-regex: 0 14 | configuration-path: .github/labeler.yml -------------------------------------------------------------------------------- /.github/workflows/new-issues.yml: -------------------------------------------------------------------------------- 1 | name: Add to project 2 | 3 | on: 4 | issues: 5 | types: [opened,transferred] 6 | 7 | jobs: 8 | add-to-project: 9 | name: Add issue to project 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/add-to-project@main 13 | id: add_project 14 | with: 15 | project-url: ${{ secrets.PROJECT_PLANNING }} 16 | github-token: ${{ secrets.BOT_TOKEN_PROJECT }} 17 | - name: Set Team 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.BOT_TOKEN_PROJECT }} 20 | run: | 21 | gh api graphql -f query=' 22 | mutation( 23 | $project: ID! 24 | $item: ID! 25 | $status_field: ID! 26 | $status_value: String! 27 | ) { 28 | updateProjectV2ItemFieldValue( 29 | input: { 30 | projectId: $project 31 | itemId: $item 32 | fieldId: $status_field 33 | value: { 34 | singleSelectOptionId: $status_value 35 | } 36 | } 37 | ) { 38 | projectV2Item { 39 | id 40 | } 41 | } 42 | }' -f project=${{ secrets.PROJECT_PLANNING_ID }} -f item=${{ steps.add_project.outputs.itemId }} -f status_field=${{ secrets.PLANNING_TEAM_FIELD_ID }} -f status_value=ed46988b --silent -------------------------------------------------------------------------------- /.github/workflows/pr-announcer-docs.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | types: [closed] 4 | branches: 5 | - 'development' 6 | - 'new/**' 7 | jobs: 8 | pr_announcer: 9 | runs-on: ubuntu-latest 10 | name: Announce pr 11 | steps: 12 | - name: Checking merged commit 13 | uses: Codeinwp/action-pr-merged-announcer@main 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} 16 | with: 17 | destination_repo: "Codeinwp/docs" 18 | issue_labels: "feedzy" 19 | -------------------------------------------------------------------------------- /.github/workflows/pr-checklist.yml: -------------------------------------------------------------------------------- 1 | name: Add labels to pull request based on checklist 2 | 3 | on: 4 | pull_request: 5 | types: [ opened, edited, synchronize, labeled, unlabeled ] 6 | branches-ignore: 7 | - 'master' 8 | - 'update_dependencies' 9 | 10 | permissions: write-all 11 | 12 | jobs: 13 | add-labels: 14 | runs-on: ubuntu-latest 15 | name: Label PR based on checklist 16 | if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name && github.actor != 'dependabot[bot]' 17 | steps: 18 | - name: Check if checklist items are completed 19 | uses: Codeinwp/gha-pr-check-helper@master 20 | with: 21 | token: ${{ secrets.BOT_TOKEN }} 22 | requireChecklist: true 23 | onlyCheckBody: true 24 | completedLabel: "pr-checklist-complete" 25 | incompleteLabel: "pr-checklist-incomplete" 26 | skipLabel: "pr-checklist-skip" 27 | -------------------------------------------------------------------------------- /.github/workflows/pr_verify_linked_issue.yml: -------------------------------------------------------------------------------- 1 | name: Verify PR 2 | 3 | on: 4 | pull_request: 5 | types: [edited, synchronize, opened, reopened] 6 | branches: 7 | - development 8 | check_run: 9 | 10 | jobs: 11 | verify_linked_issue: 12 | runs-on: ubuntu-latest 13 | if: github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' 14 | name: Ensure Pull Request has a linked issue. 15 | steps: 16 | - name: Verify Linked Issue 17 | id: verify_linked_issues 18 | uses: Codeinwp/verify-linked-issue-action@master 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} 21 | with: 22 | quiet: "true" 23 | - name: Find Comment 24 | uses: peter-evans/find-comment@v1 25 | id: find_coomment 26 | with: 27 | issue-number: ${{ github.event.pull_request.number }} 28 | comment-author: "pirate-bot" 29 | body-includes: No Linked Issue found 30 | - name: Create or update comment 31 | uses: peter-evans/create-or-update-comment@v1 32 | if: steps.verify_linked_issues.outputs.has_linked_issues != 'true' 33 | with: 34 | comment-id: ${{ steps.find_coomment.outputs.comment-id }} 35 | token: ${{ secrets.BOT_TOKEN }} 36 | issue-number: ${{ github.event.pull_request.number }} 37 | body: | 38 | :guardsman: PR Error! No Linked Issue found. Please link an issue or mention it in the body using # 39 | edit-mode: replace 40 | - name: Fail action on no issue found 41 | if: steps.verify_linked_issues.outputs.has_linked_issues == 'false' 42 | run: exit 1; 43 | - name: Delete comment 44 | uses: winterjung/comment@v1 45 | if: steps.verify_linked_issues.outputs.has_linked_issues == 'true' && steps.find_coomment.outputs.comment-id != '' 46 | with: 47 | type: delete 48 | comment_id: ${{ steps.find_coomment.outputs.comment-id }} 49 | token: ${{ secrets.BOT_TOKEN }} 50 | -------------------------------------------------------------------------------- /.github/workflows/sync-branches.yml: -------------------------------------------------------------------------------- 1 | name: Sync branches 2 | on: 3 | push: 4 | branches: 5 | - 'master' 6 | jobs: 7 | sync-branch: 8 | runs-on: ubuntu-latest 9 | if: ${{ github.repository_owner == 'Codeinwp' }} # Disable on forks 10 | steps: 11 | - uses: actions/checkout@master 12 | - name: Retrieve branch name 13 | id: retrieve-branch-name 14 | run: echo "::set-output name=branch_name::$(REF=${GITHUB_HEAD_REF:-$GITHUB_REF} && echo ${REF#refs/heads/} | sed 's/\//-/g')" 15 | - name: Merge master -> development 16 | if: ${{ steps.retrieve-branch-name.outputs.branch_name == 'master' }} 17 | uses: Codeinwp/merge-branch@master 18 | with: 19 | type: now 20 | from_branch: master 21 | target_branch: development 22 | github_token: ${{ secrets.BOT_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/sync-wporg-assets.yml: -------------------------------------------------------------------------------- 1 | name: Sync assets with wordpress.org 2 | 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - 'readme.txt' 10 | - '.wordpress-org/**' 11 | jobs: 12 | run: 13 | runs-on: ubuntu-latest 14 | if: "! contains(github.event.head_commit.message, 'chore(release)')" 15 | name: Push assets to wporg 16 | steps: 17 | - uses: actions/checkout@master 18 | - name: WordPress.org plugin asset/readme update 19 | uses: selul/action-wordpress-plugin-asset-update@develop 20 | env: 21 | SVN_PASSWORD: ${{ secrets.SVN_THEMEISLE_PASSWORD }} 22 | SVN_USERNAME: ${{ secrets.SVN_THEMEISLE_USERNAME }} -------------------------------------------------------------------------------- /.github/workflows/test-e2e.yml: -------------------------------------------------------------------------------- 1 | name: Test e2e 2 | concurrency: 3 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} 4 | cancel-in-progress: true 5 | on: 6 | push: 7 | branches-ignore: master 8 | jobs: 9 | e2e: 10 | name: E2E 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | with: 16 | node-version: "18" 17 | cache: "npm" 18 | - name: Install npm deps 19 | run: | 20 | npm ci 21 | npm run build 22 | - name: Install composer deps 23 | run: composer install --no-dev 24 | - name: Install environment 25 | run: | 26 | npm run wp-env start 27 | npm run test:e2e 28 | - name: Archive test results 29 | if: failure() 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: e2e-playwright-results 33 | path: artifacts 34 | retention-days: 1 35 | if-no-files-found: ignore 36 | -------------------------------------------------------------------------------- /.github/workflows/test-lint-js.yml: -------------------------------------------------------------------------------- 1 | name: Check JS files and build 2 | concurrency: 3 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} 4 | cancel-in-progress: true 5 | on: 6 | push: 7 | branches-ignore: 8 | - "master" 9 | - "dependabot/**" 10 | 11 | jobs: 12 | npm: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout source code 16 | uses: actions/checkout@v4 17 | - name: Setup node 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: "18" 21 | cache: "npm" 22 | - name: Install Dependencies 23 | run: npm install 24 | - name: Run JS check 25 | run: npm run lint:js 26 | - name: Check if we can build the project 27 | run: npm run build 28 | -------------------------------------------------------------------------------- /.github/workflows/test-php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Tests 2 | concurrency: 3 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.ref }} 4 | cancel-in-progress: true 5 | on: 6 | push: 7 | branches-ignore: 8 | - "master" 9 | 10 | jobs: 11 | phplint: 12 | name: PHP Lint 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Setup PHP version 16 | uses: shivammathur/setup-php@v2 17 | with: 18 | php-version: "7.4" 19 | extensions: simplexml 20 | - name: Checkout source code 21 | uses: actions/checkout@v4 22 | - name: Install composer 23 | run: | 24 | composer install --prefer-dist --no-progress 25 | - name: Run PHPCS 26 | run: composer run lint 27 | 28 | phpunit: 29 | name: PHPUnit 30 | runs-on: ubuntu-latest 31 | services: 32 | mysql: 33 | image: mysql:5.7 34 | env: 35 | MYSQL_ROOT_PASSWORD: root 36 | ports: 37 | - 3306/tcp 38 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 39 | steps: 40 | - name: Setup PHP version 41 | uses: shivammathur/setup-php@v2 42 | with: 43 | php-version: "7.4" 44 | extensions: simplexml, mysql 45 | tools: phpunit-polyfills 46 | - name: Checkout source code 47 | uses: actions/checkout@v4 48 | - name: Install Subversion 49 | run: sudo apt-get update && sudo apt-get install -y subversion 50 | - name: Install WordPress Test Suite 51 | run: | 52 | bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1:${{ job.services.mysql.ports['3306'] }} 53 | - name: Install composer 54 | run: | 55 | composer install --prefer-dist --no-progress --no-dev 56 | - name: Run phpunit 57 | run: phpunit 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | logs 4 | artifact 5 | vendor 6 | /dist 7 | cypress/integration/examples 8 | cypress/videos/* 9 | cypress/screenshots/* 10 | cypress/integration/localhost* 11 | cypress/fixtures 12 | cypress.env.json 13 | .phpunit.result.cache 14 | js/build 15 | /build 16 | artifacts 17 | .DS_Store -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/* -------------------------------------------------------------------------------- /.releaserc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | branch: master 3 | plugins: 4 | - - "@semantic-release/commit-analyzer" 5 | - preset: simple-preset 6 | releaseRules: conventional-changelog-simple-preset/release-rules 7 | - - "@semantic-release/changelog" 8 | - changelogFile: CHANGELOG.md 9 | - - "@semantic-release/release-notes-generator" 10 | - preset: simple-preset 11 | - - "@semantic-release/exec" 12 | - prepareCmd: 'export nextReleaseNotes="${nextRelease.notes}" && node bin/update-changelog.js' 13 | - - "@semantic-release/exec" 14 | - prepareCmd: grunt version::${nextRelease.version} && grunt wp_readme_to_markdown 15 | - - "@semantic-release/git" 16 | - assets: 17 | - CHANGELOG.md 18 | - readme.md 19 | - readme.txt 20 | - includes/feedzy-rss-feeds.php 21 | - css/feedzy-rss-feeds.css 22 | - feedzy-rss-feed.php 23 | - package-lock.json 24 | - package.json 25 | message: "chore(release): ${nextRelease.version} \n\n${nextRelease.notes}" 26 | - - "semantic-release-slack-bot" 27 | - notifyOnSuccess: false 28 | notifyOnFail: false 29 | markdownReleaseNotes: true 30 | branchesConfig: 31 | - pattern: master 32 | notifyOnSuccess: true 33 | notifyOnFail: false 34 | - - "@semantic-release/github" 35 | -------------------------------------------------------------------------------- /.wordpress-org/banner-1544x500-de_DE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/banner-1544x500-de_DE.png -------------------------------------------------------------------------------- /.wordpress-org/banner-1544x500-de_DE_formal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/banner-1544x500-de_DE_formal.png -------------------------------------------------------------------------------- /.wordpress-org/banner-1544x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/banner-1544x500.png -------------------------------------------------------------------------------- /.wordpress-org/banner-722x250-de_DE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/banner-722x250-de_DE.png -------------------------------------------------------------------------------- /.wordpress-org/banner-722x250-de_DE_formal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/banner-722x250-de_DE_formal.png -------------------------------------------------------------------------------- /.wordpress-org/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/banner-772x250.png -------------------------------------------------------------------------------- /.wordpress-org/icon-128x128.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/icon-128x128.gif -------------------------------------------------------------------------------- /.wordpress-org/icon-256x256.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/icon-256x256.gif -------------------------------------------------------------------------------- /.wordpress-org/screenshot-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/screenshot-1.gif -------------------------------------------------------------------------------- /.wordpress-org/screenshot-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/screenshot-2.gif -------------------------------------------------------------------------------- /.wordpress-org/screenshot-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/screenshot-3.gif -------------------------------------------------------------------------------- /.wordpress-org/screenshot-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/screenshot-4.gif -------------------------------------------------------------------------------- /.wordpress-org/screenshot-5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/screenshot-5.gif -------------------------------------------------------------------------------- /.wordpress-org/screenshot-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/screenshot-6.jpg -------------------------------------------------------------------------------- /.wordpress-org/screenshot-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/.wordpress-org/screenshot-7.jpg -------------------------------------------------------------------------------- /.wp-env.json: -------------------------------------------------------------------------------- 1 | { 2 | "core": null, 3 | "plugins": [ "." ], 4 | "lifecycleScripts": { 5 | "afterStart": "wp-env run tests-cli wp option update feedzy_legacyv5 1" 6 | 7 | }, 8 | "env": { 9 | "tests": { 10 | "config": { 11 | "E2E_TESTING": true 12 | }, 13 | "mappings": { 14 | "wp-content/themes/gutenberg-test-themes/twentytwentyone": "https://downloads.wordpress.org/theme/twentytwentyone.2.1.zip", 15 | "wp-content/themes/gutenberg-test-themes/twentytwentyfour": "https://downloads.wordpress.org/theme/twentytwentyfour.1.0.zip" 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Grunt File 3 | * 4 | * @package feedzy-rss-feeds 5 | */ 6 | module.exports = function (grunt) { 7 | grunt.initConfig({ 8 | wp_readme_to_markdown: { 9 | feedzy : { 10 | files: { 11 | 'readme.md': 'readme.txt' 12 | } 13 | }, 14 | }, 15 | version: { 16 | 17 | project: { 18 | src: [ 19 | 'package.json' 20 | ] 21 | }, 22 | style: { 23 | options: { 24 | prefix: 'Version\\:\.*\\s' 25 | }, 26 | src: [ 27 | 'feedzy-rss-feed.php', 28 | 'css/feedzy-rss-feeds.css', 29 | ] 30 | }, 31 | readmetxt: { 32 | options: { 33 | prefix: 'Stable tag:\\s*' 34 | }, 35 | src: [ 36 | 'readme.txt' 37 | ] 38 | }, 39 | class: { 40 | options: { 41 | prefix: '\\.*version\.*\\s=\.*\\s\'' 42 | }, 43 | src: [ 44 | 'includes/feedzy-rss-feeds.php', 45 | ] 46 | } 47 | } 48 | }); 49 | grunt.loadNpmTasks('grunt-version'); 50 | grunt.loadNpmTasks('grunt-wp-readme-to-markdown'); 51 | }; 52 | -------------------------------------------------------------------------------- /bin/dist.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | BUILD_VERSION=$(node -pe "require('./package.json').version") 4 | export BUILD_VERSION 5 | BUILD_NAME=$(node -pe "require('./package.json').name") 6 | export BUILD_NAME 7 | 8 | echo "BUILD_NAME=$BUILD_NAME" >> $GITHUB_ENV 9 | echo "BUILD_VERSION=$BUILD_VERSION" >> $GITHUB_ENV 10 | 11 | if [ ! -d "dist" ]; then 12 | mkdir "dist" 13 | fi 14 | 15 | if [ ! -d "artifact" ]; then 16 | mkdir "artifact" 17 | fi 18 | 19 | rsync -rc --exclude-from ".distignore" "./" "dist/$BUILD_NAME" 20 | 21 | cd dist 22 | zip -r "../artifact/$BUILD_NAME" "./$BUILD_NAME/" 23 | cd - -------------------------------------------------------------------------------- /bin/generatePlaygroundPreviewLink.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Read GitHub Action environment variables. 3 | const repoName = process.env.GITHUB_REPO_NAME || ''; 4 | const branchName = process.env.DEV_ZIP_BRANCH_NAME || ''; 5 | const gitSha8 = process.env.DEV_ZIP_GIT_SHA_8 || ''; 6 | 7 | // Create the blueprint object with necessary schema and options. 8 | const blueprint = { 9 | preferredVersions: { 10 | php: '8.0', 11 | }, 12 | plugins: [ 13 | 'wpide', 14 | `https://verti-artifacts.s3.amazonaws.com/${repoName}-${branchName}-${gitSha8}/feedzy-rss-feeds.zip`, 15 | ], 16 | login: true, 17 | landingPage: '/wp-admin/post-new.php?post_type=feedzy_imports', 18 | features: { 19 | networking: true, 20 | }, 21 | }; 22 | 23 | // Convert the blueprint object to JSON and then encode it in Base64. 24 | const blueprintJson = JSON.stringify(blueprint); 25 | const encodedBlueprint = Buffer.from(blueprintJson).toString('base64'); 26 | 27 | // Output the full preview link. 28 | process.stdout.write('https://playground.wordpress.net/#' + encodedBlueprint); 29 | -------------------------------------------------------------------------------- /bin/update-changelog.js: -------------------------------------------------------------------------------- 1 | const replace = require('replace-in-file'); 2 | 3 | async function updateChangelog() { 4 | const options = { 5 | files: 'readme.txt', 6 | from: /== Changelog ==[\s\S]*?\[See changelog for all versions\]/, 7 | to: (match) => { 8 | return `== Changelog ==\n\n${process.env.nextReleaseNotes}\n\n[See changelog for all versions]`; 9 | }, 10 | encoding: 'utf8', 11 | }; 12 | try { 13 | const results = await replace(options); 14 | console.log('Replacement results:', results); 15 | } catch (error) { 16 | console.error('Error occurred:', error); 17 | } 18 | } 19 | 20 | updateChangelog(); -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codeinwp/feedzy-rss-feeds", 3 | "description": "Feedzy RSS feeds for WordPress.", 4 | "keywords": [ 5 | "wordpress", 6 | "feeds", 7 | "rss" 8 | ], 9 | "homepage": "https://themeisle.com/plugins/feedzy-rss-feeds/", 10 | "license": "GPL-2.0+", 11 | "authors": [ 12 | { 13 | "name": "ThemeIsle Team", 14 | "email": "friends@themeisle.com.com", 15 | "homepage": "https://themeisle.com" 16 | } 17 | ], 18 | "type": "wordpress-plugin", 19 | "support": { 20 | "issues": "https://github.com/Codeinwp/feedzy-rss-feeds/issues", 21 | "source": "https://github.com/Codeinwp/feedzy-rss-feeds" 22 | }, 23 | "require": { 24 | "codeinwp/themeisle-sdk": "^3.3" 25 | }, 26 | "autoload": { 27 | "files": [ 28 | "vendor/codeinwp/themeisle-sdk/load.php" 29 | ] 30 | }, 31 | "scripts": { 32 | "format": "phpcbf --standard=phpcs.xml --report-summary --report-source", 33 | "lint": "phpcs --standard=phpcs.xml", 34 | "test": "phpunit" 35 | }, 36 | "minimum-stability": "dev", 37 | "prefer-stable": true, 38 | "config": { 39 | "optimize-autoloader": true, 40 | "platform": { 41 | "php": "7.2" 42 | }, 43 | "allow-plugins": { 44 | "dealerdirect/phpcodesniffer-composer-installer": true 45 | } 46 | }, 47 | "require-dev": { 48 | "wp-coding-standards/wpcs": "^3.1", 49 | "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0", 50 | "phpcompatibility/phpcompatibility-wp": "^2.1", 51 | "yoast/phpunit-polyfills": "^4.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /css/feedzy-elementor-widget.css: -------------------------------------------------------------------------------- 1 | .feedzy-el-full-width .elementor-control-unit-2 { 2 | width: 100%; 3 | } 4 | .elementor-control-type-section.fz-feat-locked .eicon-lock { 5 | position: absolute; 6 | top: 13px; 7 | right: 20px; 8 | display: block; 9 | color: #64666a; 10 | } 11 | .fz-feat-locked:not(.elementor-control-type-section) > div .elementor-control-field { 12 | opacity: .2; 13 | } 14 | .fz-feat-locked:not(.elementor-control-type-section) > div .elementor-control-field { 15 | pointer-events: none; 16 | } 17 | .fz-feat-locked:not(.elementor-control-type-section) { 18 | cursor: not-allowed; 19 | } 20 | 21 | .elementor-choices.fz-layout-choices{ 22 | height: auto; 23 | } 24 | .fz-layout-list{ 25 | display: flex; 26 | flex-wrap: wrap; 27 | margin: 0 -4px; 28 | } 29 | .fz-layout-list li{ 30 | width: 50%; 31 | padding: 0 4px 15px; 32 | } 33 | .fz-layout-list li .elementor-control-unit{ 34 | display: block !important; 35 | } 36 | .fz-layout-list li .img{ 37 | height: 66px; 38 | margin-bottom: 8px; 39 | background: #34383C; 40 | display: flex; 41 | align-items: center; 42 | justify-content: center; 43 | position: relative; 44 | } 45 | .fz-layout-list li .img:after{ 46 | content: ""; 47 | position: absolute; 48 | left: 0; 49 | bottom: 0; 50 | width: 100%; 51 | height: 4px; 52 | background: #71D7F7; 53 | opacity: 0; 54 | } 55 | .fz-layout-list li input:checked ~ .elementor-control-unit .img{ 56 | background: #64666A; 57 | } 58 | .fz-layout-list li input:checked ~ .elementor-control-unit .img:after{ 59 | opacity: 1; 60 | } 61 | 62 | .fz-el-light-mode .fz-layout-list li .img{ 63 | background: #E6E9EC; 64 | } 65 | .fz-el-light-mode .fz-layout-list li input:checked ~ .elementor-control-unit .img{ 66 | background: #C7CCD1; 67 | } 68 | 69 | .fz-pro-notice{ 70 | text-align: center; 71 | max-width: 202px; 72 | margin: 0 auto; 73 | padding: 56px 0 30px; 74 | } 75 | .fz-pro-notice .fz-logo{ 76 | padding-bottom: 20px; 77 | } 78 | .fz-pro-notice h3{ 79 | font-size: 16px; 80 | line-height: 19px; 81 | font-weight: 700; 82 | color: #E0E1E3; 83 | margin-bottom: 5px; 84 | } 85 | .fz-light-mode.fz-pro-notice h3{ 86 | color: #6D7882; 87 | } 88 | .fz-pro-notice p{ 89 | font-weight: 400; 90 | font-size: 11px; 91 | line-height: 15px; 92 | color: #E0E1E3; 93 | margin-bottom: 16px; 94 | } 95 | .fz-light-mode.fz-pro-notice p{ 96 | color: #6D7882; 97 | } 98 | .docs-btn a.fz-upgrade-link{ 99 | display: inline-block; 100 | background: #4268CF; 101 | padding: 6px 12px; 102 | border-radius: 3px; 103 | font-weight: 700; 104 | font-size: 11px; 105 | line-height: 13px; 106 | text-transform: uppercase; 107 | color: #FFFFFF; 108 | } 109 | .docs-btn a.fz-upgrade-link:hover, 110 | .docs-btn a.fz-upgrade-link:focus{ 111 | color: #FFFFFF; 112 | } 113 | .docs-btn span{ 114 | display: block; 115 | font-style: italic; 116 | font-weight: 400; 117 | font-size: 11px; 118 | line-height: 13px; 119 | color: #757575; 120 | padding-top: 8px; 121 | } 122 | .docs-btn span a { 123 | color: #757575; 124 | } 125 | .fz-light-mode .docs-btn span { 126 | color: #757575; 127 | } 128 | 129 | .fz-upsell-notice.light-mode { 130 | padding: 12px; 131 | background: #F3FCFF; 132 | border-left: 3px solid #4268CF; 133 | font-style: italic; 134 | color: #6D7882; 135 | margin: 10px 15px; 136 | position: relative; 137 | } 138 | .fz-upsell-notice.light-mode a { 139 | color: #4268CF; 140 | font-weight: 700; 141 | } 142 | .fz-upsell-notice.light-mode .remove-alert { 143 | color: #6F7882; 144 | font-weight: 400; 145 | background: none; 146 | border: none; 147 | outline: none; 148 | cursor: pointer; 149 | position: absolute; 150 | top: 4px; 151 | right: 4px; 152 | } 153 | .fz-upsell-notice.light-mode .remove-alert:hover { 154 | color: #6F7882; 155 | } 156 | 157 | .fz-upsell-notice.dark-mode { 158 | padding: 12px; 159 | background: #4C4F56; 160 | border-left: 3px solid #71D7F7; 161 | font-style: italic; 162 | color: #E0E1E3; 163 | margin: 10px 15px; 164 | position: relative; 165 | } 166 | .fz-upsell-notice.dark-mode a { 167 | color: #71D7F7; 168 | font-weight: 700; 169 | } 170 | .fz-upsell-notice.dark-mode .remove-alert { 171 | color: #E0E1E3; 172 | font-weight: 400; 173 | background: none; 174 | border: none; 175 | outline: none; 176 | cursor: pointer; 177 | position: absolute; 178 | top: 4px; 179 | right: 4px; 180 | } 181 | .fz-upsell-notice.dark-mode .remove-alert:hover { 182 | color: #E0E1E3; 183 | } -------------------------------------------------------------------------------- /css/metabox-settings.css: -------------------------------------------------------------------------------- 1 | /** 2 | * import-metabox-edit.css 3 | * 4 | * @since 1.2.0 5 | * @package feedzy-rss-feeds-pro 6 | */ 7 | 8 | 9 | @media (max-width: 1024px){ 10 | .h1{ 11 | font-size: 34px; 12 | line-height: 43px; 13 | } 14 | .h2{ 15 | font-size: 24px; 16 | line-height: 30px; 17 | } 18 | .fz-tabs-menu{border-bottom: 1px solid #D9D9D9;} 19 | .fz-tabs-menu ul{flex-wrap: nowrap; overflow: auto; border-bottom: 0;} 20 | .fz-tabs-menu ul li{flex-shrink: 0;} 21 | .support-box-list > ul > li{width: 100%;} 22 | .fz-document-list > ul > li{width: 50%;} 23 | .fz-pro-features-table-header{display: none;} 24 | .fz-pro-features-table-body .fz-pro-features-table-row li{padding: 15px;} 25 | .fz-pro-features-table-row .features-info{width: 100%;} 26 | .fz-pro-features-table-row .free, .fz-pro-features-table-row .pro{width: 50%;} 27 | .fz-pro-features-table-row .free:before, .fz-pro-features-table-row .pro:before{content: attr(data-label); font-size: 16px; font-weight: 700; color: #757575; display: block; padding-right: 8px;} 28 | } 29 | 30 | @media (max-width: 767px){ 31 | .h1{font-size: 28px; line-height: 37px;} 32 | .h2{font-size: 20px; line-height: 26px;} 33 | .feedzy-header{padding: 15px 0;} 34 | .feedzy-header .feedzy-logo .feedzy-logo-icon{max-width: 40px;} 35 | .feedzy-header .feedzy-logo .feedzy-logo-icon img{max-width: 100%;} 36 | .post-type-feedzy_imports:not(.edit-php) #wpcontent, .feedzy_page_feedzy-support #wpcontent, .feedzy_page_feedzy-settings #wpcontent{padding-top: 46px;} 37 | #wpbody{padding-top: 0;} 38 | .feedzy-accordion .feedzy-accordion-item{margin-bottom: 15px;} 39 | .feedzy-accordion-item .feedzy-accordion-item__button{padding: 15px 60px 15px 15px;} 40 | .feedzy-accordion-item .feedzy-accordion__icon{width: 38px; height: 38px; right: 15px; margin-top: -19px;} 41 | .feedzy-accordion-item .feedzy-accordion-item__content{padding-left: 15px; padding-right: 15px;} 42 | .fz-input-group{flex-wrap: wrap;} 43 | .fz-input-group .fz-input-group-right{width: 100%; padding-left: 0; padding-top: 8px;} 44 | .fz-input-group .fz-input-group-right .btn.dropdown-toggle{width: 100%;} 45 | .fz-form-wrap .form-block.form-block-two-column .fz-left{width: 100%; padding-right: 0; padding-bottom: 24px;} 46 | .fz-form-wrap .form-block.form-block-two-column .fz-right{width: 100%;} 47 | .fz-form-action .fz-left{width: 100%; padding-bottom: 16px;} 48 | .fz-form-action .fz-left .btn{width: 100%; display: block; text-align: center;} 49 | .fz-form-action .fz-right{width: 100%; flex-direction: column;} 50 | .fz-form-action .fz-right .btn + .btn{margin-left: 0; margin-top: 8px;} 51 | .fz-form-row .fz-form-col-6, .fz-form-row .fz-form-col-4, .fz-form-row .fz-form-col-8{width: 100%;} 52 | .fz-document-list > ul > li{width: 100%;} 53 | .fz-help-improve-box .fz-left{width: 100%;} 54 | .fz-help-improve-box .fz-right{width: 100%;} 55 | .fz-tabs-menu ul {flex-wrap: wrap;} 56 | .fz-tabs-menu ul li{flex-shrink: 0; width: 50%;} 57 | .fz-tabs-menu ul li a{padding: 0px; border: 1px solid transparent;} 58 | .fz-tabs-menu ul li a:after{display: none} 59 | .fz-tabs-menu ul li a:focus{border: 1px solid #4268CF} 60 | .fz-tabs-menu ul li a.active{border: 0;} 61 | } 62 | 63 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi), only screen and (min-resolution: 1.5dppx) { 64 | .fz-form-wrap .chosen-rtl .chosen-search input[type="text"], 65 | .fz-form-wrap .chosen-container-single .chosen-single abbr, 66 | .fz-form-wrap .chosen-container-single .chosen-single div b, 67 | .fz-form-wrap .chosen-container-single .chosen-search input[type="text"], 68 | .fz-form-wrap .chosen-container-multi .chosen-choices .search-choice .search-choice-close, 69 | .fz-form-wrap .chosen-container .chosen-results-scroll-down span, 70 | .fz-form-wrap .chosen-container .chosen-results-scroll-up span { 71 | background: #ffffff !important; 72 | background-repeat: no-repeat !important; 73 | background-size: inherit !important; 74 | } 75 | } -------------------------------------------------------------------------------- /img/Protect-your-Brand.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/Protect-your-Brand.jpg -------------------------------------------------------------------------------- /img/Unlimited-Content.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/Unlimited-Content.jpg -------------------------------------------------------------------------------- /img/World-class-support.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/World-class-support.jpg -------------------------------------------------------------------------------- /img/alternate-file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /img/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /img/dark-feedzy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/dark-feedzy-logo.png -------------------------------------------------------------------------------- /img/dark-mode-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/dark-mode-default.png -------------------------------------------------------------------------------- /img/dark-mode-style1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/dark-mode-style1.png -------------------------------------------------------------------------------- /img/dark-mode-style2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/dark-mode-style2.png -------------------------------------------------------------------------------- /img/el-lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/el-lock.png -------------------------------------------------------------------------------- /img/external-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/external-link.png -------------------------------------------------------------------------------- /img/features-affiliate-ready.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/features-affiliate-ready.jpg -------------------------------------------------------------------------------- /img/features-caching.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/features-caching.jpg -------------------------------------------------------------------------------- /img/features-feed-to-post.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/features-feed-to-post.jpg -------------------------------------------------------------------------------- /img/features-templates.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/features-templates.jpg -------------------------------------------------------------------------------- /img/features-widgets-support.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/features-widgets-support.jpg -------------------------------------------------------------------------------- /img/feed-to-post.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/feed-to-post.jpg -------------------------------------------------------------------------------- /img/feedzy-default-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/feedzy-default-template.png -------------------------------------------------------------------------------- /img/feedzy-demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/feedzy-demo.jpg -------------------------------------------------------------------------------- /img/feedzy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/feedzy-logo.png -------------------------------------------------------------------------------- /img/feedzy-rss-feeds-wordai.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/feedzy-rss-feeds-wordai.jpg -------------------------------------------------------------------------------- /img/feedzy-style1-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/feedzy-style1-template.png -------------------------------------------------------------------------------- /img/feedzy-style2-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/feedzy-style2-template.png -------------------------------------------------------------------------------- /img/feedzy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/feedzy.png -------------------------------------------------------------------------------- /img/feedzy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Combined Shape 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /img/improve-feedzy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/improve-feedzy.png -------------------------------------------------------------------------------- /img/in-feedzy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/in-feedzy.jpg -------------------------------------------------------------------------------- /img/light-feedzy-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/light-feedzy-logo.png -------------------------------------------------------------------------------- /img/light-mode-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/light-mode-default.png -------------------------------------------------------------------------------- /img/light-mode-style1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/light-mode-style1.png -------------------------------------------------------------------------------- /img/light-mode-style2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/light-mode-style2.png -------------------------------------------------------------------------------- /img/mask-loader.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/mask-loader.jpg -------------------------------------------------------------------------------- /img/newsletter-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/newsletter-img.png -------------------------------------------------------------------------------- /img/rephrase-feeds-content.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/rephrase-feeds-content.jpg -------------------------------------------------------------------------------- /img/shortcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/shortcode.jpg -------------------------------------------------------------------------------- /img/spin_light.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/spin_light.gif -------------------------------------------------------------------------------- /img/validate-RSS-feed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/img/validate-RSS-feed.jpg -------------------------------------------------------------------------------- /includes/admin/feedzy-rss-feeds-options.php: -------------------------------------------------------------------------------- 1 | init(); 42 | } 43 | 44 | return self::$instance; 45 | } 46 | 47 | /** 48 | * Init the default values of the options class. 49 | */ 50 | public function init() { 51 | self::$instance->options = get_option( Feedzy_Rss_Feeds::get_plugin_name(), array() ); 52 | if ( ! is_array( self::$instance->options ) ) { 53 | self::$instance->options = array(); 54 | } 55 | } 56 | 57 | /** 58 | * Get the key option value from DB. 59 | * 60 | * @param string $key The key name of the option. 61 | * 62 | * @return bool|mixed The value of the option 63 | */ 64 | public function get_var( $key ) { 65 | if ( isset( self::$instance->options[ $key ] ) ) { 66 | return self::$instance->options[ $key ]; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | /** 73 | * Setter method for updating the options array. 74 | * 75 | * @param string $key The name of option. 76 | * @param string $value The value of the option. 77 | * 78 | * @return bool|mixed The value of the option. 79 | */ 80 | public function set_var( $key, $value = '' ) { 81 | self::$instance->options[ $key ] = apply_filters( 'feedzy_pre_set_option_' . $key, $value ); 82 | 83 | return update_option( Feedzy_Rss_Feeds::get_plugin_name(), self::$instance->options ); 84 | } 85 | } 86 | }// End if(). 87 | -------------------------------------------------------------------------------- /includes/admin/feedzy-rss-feeds-upgrader.php: -------------------------------------------------------------------------------- 1 | get_var( 'db_version' ); 36 | if ( $db_version === false ) { 37 | feedzy_options()->set_var( 'db_version', $php_version ); 38 | $this->db_version = $php_version; 39 | } else { 40 | if ( feedzy_options()->get_var( 'is_new' ) === false ) { 41 | feedzy_options()->set_var( 'is_new', 'no' ); 42 | } 43 | $this->db_version = $db_version; 44 | } 45 | $this->php_version = $php_version; 46 | } 47 | 48 | /** 49 | * Check if we need to run an upgrade or not. 50 | */ 51 | public function check() { 52 | if ( version_compare( $this->db_version, $this->php_version ) === - 1 ) { 53 | do_action( 'feedzy_upgrade_to_' . self::version_to_hook( $this->php_version ), $this->db_version ); 54 | } 55 | } 56 | 57 | /** 58 | * Normalize version to be used in hooks. 59 | * 60 | * @param string $version In format 2.0.0. 61 | * 62 | * @return string Version format 2_0_0. 63 | */ 64 | public static function version_to_hook( $version ) { 65 | return str_replace( '.', '_', $version ); 66 | } 67 | } 68 | }// End if(). 69 | -------------------------------------------------------------------------------- /includes/elementor/controls/datetime-local.php: -------------------------------------------------------------------------------- 1 | true, 42 | 'picker_options' => array(), 43 | ); 44 | } 45 | 46 | /** 47 | * Render date time control output in the editor. 48 | * 49 | * Used to generate the control HTML in the editor using Underscore JS 50 | * template. The variables for the class are available using `data` JS 51 | * object. 52 | * 53 | * @since 1.0.0 54 | * @access public 55 | */ 56 | public function content_template() { 57 | ?> 58 |
59 | 60 |
61 | 62 |
63 |
64 | <# if ( data.description ) { #> 65 |
{{{ data.description }}}
66 | <# } #> 67 | 37 | 40 |

' . esc_html__( 'Discover Feedzy Pro', 'feedzy-rss-feeds' ) . '

41 |

' . esc_html__( 'With Feedzy Pro you get more features, like Custom Templates, Magic Tags, Keywords filters and much more.', 'feedzy-rss-feeds' ) . '

42 |
43 | ' . esc_html__( 'Learn more', 'feedzy-rss-feeds' ) . ' 44 | ' . esc_html__( 'Open Feedzy docs', 'feedzy-rss-feeds' ) . ' 45 |
46 | '; 47 | } 48 | $upsell_notice = '
'; 49 | 50 | $upsell_notice .= wp_sprintf( 51 | // translators: %1$s: opening anchor tag, %2$s: closing anchor tag 52 | __( 'Create Reusable Elementor Templates with Feedzy\'s Dynamic Tags Using Feedzy Pro. %1$sLearn more%2$s', 'feedzy-rss-feeds' ), 53 | '
', 54 | '' 55 | ); 56 | 57 | $upsell_notice .= '
'; 58 | wp_localize_script( 59 | 'feedzy-elementor', 60 | 'FeedzyElementorEditor', 61 | array( 62 | 'notice' => $notice_text, 63 | 'security' => wp_create_nonce( FEEDZY_BASEFILE ), 64 | 'pro_title_text' => __( 'Unlock this feature with Feedzy Pro', 'feedzy-rss-feeds' ), 65 | 'upsell_notice' => ( ! feedzy_is_pro() && ! \Feedzy_Rss_Feeds_Ui::had_dismissed_notice() ) ? $upsell_notice : '', 66 | ) 67 | ); 68 | } 69 | 70 | /** 71 | * Render choose control output in the editor. 72 | * 73 | * Used to generate the control HTML in the editor using Underscore JS 74 | * template. The variables for the class are available using `data` JS 75 | * object. 76 | * 77 | * @since 1.0.0 78 | * @access public 79 | */ 80 | public function content_template() { 81 | $control_uid_input_type = '{{value}}'; 82 | ?> 83 |
84 |
85 |
86 |
    87 | <# _.each( data.template_options, function( options, value ) { 88 | var checked = ''; 89 | if ( data.controlValue === '' ) { 90 | data.controlValue = 'default'; 91 | } 92 | if ( data.controlValue === value ) { 93 | checked = ' checked'; 94 | } 95 | #> 96 |
  • 97 | 98 | 104 |
  • 105 | <# } ); #> 106 |
107 |
108 |
109 |
110 | 111 | <# if ( data.description ) { #> 112 |
{{{ data.description }}}
113 | <# } #> 114 | array(), 131 | 'toggle' => true, 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /includes/elementor/feedzy-rss-feeds-elementor.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class Feedzy_Rss_Feeds_Elementor { 17 | 18 | /** 19 | * Enqueue scripts. 20 | */ 21 | public function feedzy_elementor_widgets_assets() { 22 | wp_register_style( 'feedzy-elementor', FEEDZY_ABSURL . 'css/feedzy-elementor-widget.css', array(), true ); 23 | wp_enqueue_style( 'feedzy-elementor' ); 24 | } 25 | 26 | /** 27 | * Editor frontend before script. 28 | */ 29 | public function feedzy_elementor_before_enqueue_scripts() { 30 | wp_register_style( 'feedzy-rss-feeds-elementor', FEEDZY_ABSURL . 'css/feedzy-rss-feeds.css', array( 'elementor-frontend' ), true, 'all' ); 31 | wp_enqueue_style( 'feedzy-rss-feeds-elementor' ); 32 | } 33 | 34 | /** 35 | * Register feedzy widget. 36 | * 37 | * @return void 38 | */ 39 | public function feedzy_elementor_widgets_registered() { 40 | // We check if the Elementor plugin has been installed / activated. 41 | if ( defined( 'ELEMENTOR_PATH' ) && class_exists( 'Elementor\Widget_Base' ) ) { 42 | require_once FEEDZY_ABSPATH . '/includes/elementor/widgets/register-widget.php'; 43 | \Elementor\Plugin::instance()->widgets_manager->register( new Feedzy_Register_Widget() ); 44 | 45 | add_action( 'elementor/editor/after_enqueue_styles', array( $this, 'feedzy_elementor_widgets_assets' ) ); 46 | } 47 | } 48 | 49 | /** 50 | * Register datetime-local control. 51 | * 52 | * @param \Elementor\Controls_Manager $controls_manager Elementor controls manager. 53 | * @return void 54 | */ 55 | public function feedzy_elementor_register_datetime_local_control( $controls_manager ) { 56 | require_once FEEDZY_ABSPATH . '/includes/elementor/controls/datetime-local.php'; 57 | require_once FEEDZY_ABSPATH . '/includes/elementor/controls/template-layout.php'; 58 | $controls_manager->register( new \Elementor\Control_Date_Time_Local() ); 59 | $controls_manager->register( new \Elementor\Control_Template_Layout() ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /includes/feedzy-rss-feeds-activator.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class Feedzy_Rss_Feeds_Activator { 23 | 24 | /** 25 | * Plugin activation action. 26 | * 27 | * Triggers the plugin activation action on plugin activate. 28 | * 29 | * @since 3.0.0 30 | * @access public 31 | */ 32 | public static function activate() { 33 | $options = get_option( Feedzy_Rss_Feeds::get_plugin_name(), array() ); 34 | $is_fresh_install = get_option( 'feedzy_fresh_install', false ); 35 | $old_logger_option = get_option( 'feedzy_logger_flag', 'no' ); 36 | if ( $old_logger_option === 'yes' ) { 37 | update_option( 'feedzy_rss_feeds_logger_flag', 'yes' ); 38 | update_option( 'feedzy_logger_flag', 'no' ); 39 | } 40 | if ( ! isset( $options['is_new'] ) ) { 41 | update_option( 42 | Feedzy_Rss_Feeds::get_plugin_name(), 43 | array( 44 | 'is_new' => 'yes', 45 | ) 46 | ); 47 | } 48 | if ( ! defined( 'TI_CYPRESS_TESTING' ) && false === $is_fresh_install ) { 49 | update_option( 'feedzy_fresh_install', '1' ); 50 | } 51 | add_option( 'feedzy-activated', true ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /includes/feedzy-rss-feeds-deactivator.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class Feedzy_Rss_Feeds_Deactivator { 23 | 24 | /** 25 | * Short Description. (use period) 26 | * 27 | * Long Description. 28 | * 29 | * @since 3.0.0 30 | * @access public 31 | */ 32 | public static function deactivate() { 33 | delete_option( 'feedzy-activated' ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /includes/layouts/feedzy-documentation.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 |
13 |
    14 |
  • 15 |
    16 |
    17 | 18 |
    19 |
    20 |

    21 |

    22 |
    23 | 24 |
    25 |
    26 |
    27 |
  • 28 |
  • 29 |
    30 |
    31 | 32 |
    33 |
    34 |

    35 |

    36 | 40 |

    41 |
    42 | 43 |
    44 |
    45 |
    46 |
  • 47 |
  • 48 |
    49 |
    50 | 51 |
    52 |
    53 |

    54 |

    55 | 60 |

    61 |
    62 | 63 |
    64 |
    65 |
    66 |
  • 67 |
  • 68 |
    69 |
    70 | 71 |
    72 |
    73 |

    74 |

    75 |
    76 | 77 |
    78 |
    79 |
    80 |
  • 81 |
  • 82 |
    83 |
    84 | 85 |
    86 |
    87 |

    88 |

    89 |
    90 | 91 |
    92 |
    93 |
    94 |
  • 95 |
  • 96 |
    97 |
    98 | 99 |
    100 |
    101 |

    102 |

    103 |
    104 | 105 |
    106 |
    107 |
    108 |
  • 109 |
110 |
111 | 112 |
113 |
114 | -------------------------------------------------------------------------------- /includes/layouts/feedzy-improve.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 |
8 |

9 |

10 | 16 |

17 |
18 | 19 |
20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /includes/layouts/feedzy-support.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 10 | 11 |
12 |
13 |
14 |
15 |
16 | 35 |
36 |
37 |
38 |
39 |
40 |
    41 |
  • 42 | 44 |
  • 45 |
  • 46 | 48 |
  • 49 | 50 |
  • 51 | 53 |
  • 54 | 55 |
  • 56 | 58 |
  • 59 | 60 | 61 |
  • 62 | 63 |
  • 64 | 65 |
66 |
67 | 68 | 89 |
90 |
91 | 92 |
93 |
94 |

95 | 99 |

100 | ', 106 | '' 107 | ) 108 | ); 109 | ?> 110 |

111 | 112 | 113 | 114 | 117 |

118 | 119 | 122 |
123 |
124 | 125 |
126 |
127 | -------------------------------------------------------------------------------- /includes/layouts/header.php: -------------------------------------------------------------------------------- 1 | 18 |
19 |
20 |
21 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /includes/layouts/integration.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 17 | notice ) { ?> 18 |

notice ); ?>

19 | 20 | 21 | error ) { ?> 22 |

error ); ?>

23 | 24 | 25 |
26 | 27 |
28 | 29 | 35 | 36 |
37 |
38 |
39 |
40 | 59 |
60 |
61 |
62 |
63 |
64 |
    65 | $label ) { 69 | ?> 70 |
  • 71 | 73 |
  • 74 | 78 |
79 |
80 | 81 |
82 | 96 | 97 | 98 | 99 | 104 |
105 | 106 |
107 | 110 |
111 | 112 |
113 |
114 | 115 |
116 | 117 |
118 |
119 |
120 | -------------------------------------------------------------------------------- /includes/util/feedzy-rss-feeds-util-scheduler.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class Feedzy_Rss_Feeds_Util_Scheduler { 21 | 22 | /** 23 | * Check if an action hook is scheduled. 24 | * 25 | * @param string $hook The hook to check. 26 | * @param array $args Optional. Arguments to pass to the hook 27 | * 28 | * @return bool|int 29 | */ 30 | public static function is_scheduled( string $hook, array $args = array() ) { 31 | if ( function_exists( 'as_next_scheduled_action' ) ) { 32 | // For older versions of AS. 33 | return as_next_scheduled_action( $hook, $args ); 34 | } 35 | if ( function_exists( 'as_has_scheduled_action' ) ) { 36 | return as_has_scheduled_action( $hook, $args ); 37 | } 38 | 39 | return wp_next_scheduled( $hook, $args ); 40 | } 41 | 42 | /** 43 | * Clear scheduled hook. 44 | * 45 | * @param string $hook The name of the hook to clear. 46 | * @param array $args Optional. Arguments that were to be passed to the hook's callback function. Default empty array. 47 | * @return mixed The scheduled action ID if a scheduled action was found, or null if no matching action found. If WP_Cron is used, on success an integer indicating number of events unscheduled, false or WP_Error if unscheduling one or more events fail. 48 | */ 49 | public static function clear_scheduled_hook( $hook, $args = array() ) { 50 | if ( function_exists( 'as_unschedule_all_actions' ) ) { 51 | return as_unschedule_all_actions( $hook, $args ); 52 | } 53 | 54 | return wp_clear_scheduled_hook( $hook, $args ); 55 | } 56 | 57 | /** 58 | * Schedule an event. 59 | * 60 | * @param int $time The first time that the event will occur. 61 | * @param string $recurrence How often the event should recur. See wp_get_schedules() for accepted values. 62 | * @param string $hook The name of the hook that will be triggered by the event. 63 | * @param array $args Optional. Arguments to pass to the hook's callback function. Default empty array. 64 | * @return integer|bool|WP_Error The action ID if Action Scheduler is used. True if event successfully scheduled, False or WP_Error on failure if WP Cron is used. 65 | */ 66 | public static function schedule_event( $time, $recurrence, $hook, $args = array() ) { 67 | if ( function_exists( 'as_schedule_recurring_action' ) ) { 68 | $schedules = wp_get_schedules(); 69 | if ( isset( $schedules[ $recurrence ] ) ) { 70 | $interval = $schedules[ $recurrence ]['interval']; 71 | return as_schedule_recurring_action( $time, $interval, $hook, $args ); 72 | } 73 | } 74 | 75 | return wp_schedule_event( $time, $recurrence, $hook, $args ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /includes/util/feedzy-rss-feeds-util-simplepie.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | class Feedzy_Rss_Feeds_Util_SimplePie extends SimplePie { 27 | 28 | /** 29 | * The shortcode attributes. 30 | * 31 | * @access private 32 | * @var array $sc The shortcode attributes. 33 | */ 34 | private static $sc; 35 | 36 | /** 37 | * Whether custom sorting is enabled. 38 | * 39 | * @access private 40 | * @var bool $custom_sorting Whether custom sorting is enabled. 41 | */ 42 | private static $custom_sorting = false; 43 | 44 | /** 45 | * Initialize the class and set its properties. 46 | * 47 | * @access public 48 | * 49 | * @param array $sc The shortcode attributes. 50 | */ 51 | public function __construct( $sc ) { 52 | self::$sc = $sc; 53 | if ( array_key_exists( 'sort', self::$sc ) && ! empty( self::$sc['sort'] ) ) { 54 | if ( 'date_desc' === self::$sc['sort'] ) { 55 | $this->enable_order_by_date( true ); 56 | } else { 57 | self::$custom_sorting = true; 58 | } 59 | } 60 | parent::__construct(); 61 | } 62 | 63 | /** 64 | * Sorting callback for items 65 | * 66 | * @access public 67 | * @param SimplePie $a The SimplePieItem. 68 | * @param SimplePie $b The SimplePieItem. 69 | * @return boolean 70 | */ 71 | public static function sort_items( $a, $b ) { 72 | if ( self::$custom_sorting ) { 73 | switch ( self::$sc['sort'] ) { 74 | case 'title_desc': 75 | return $a->get_title() <= $b->get_title(); 76 | case 'title_asc': 77 | return $a->get_title() > $b->get_title(); 78 | case 'date_asc': 79 | return $a->get_date( 'U' ) > $b->get_date( 'U' ); 80 | } 81 | } 82 | return parent::sort_items( $a, $b ); 83 | } 84 | 85 | /** 86 | * Return the filename (i.e. hash, without path and without extension) of the file to cache a given URL. 87 | * 88 | * @param string $url The URL of the feed to be cached. 89 | * @return string A filename (i.e. hash, without path and without extension). 90 | */ 91 | public function get_cache_filename( $url ) { 92 | // Append custom parameters to the URL to avoid cache pollution in case of multiple calls with different parameters. 93 | $url .= $this->force_feed ? '#force_feed' : ''; 94 | return call_user_func( $this->cache_name_function, $url ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /includes/views/amazon-product-advertising-view.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | ', 12 | '' 13 | ); 14 | echo wp_kses_post( $content ); 15 | ?> 16 |
17 |
18 |
19 | Here are the available Amazon domains: %3$s', 'feedzy-rss-feeds' ), 23 | 'amazon.[extension]?keyword=Laptop', 24 | 'amazon.com?asin=ASIN_1|ASIN_2', 25 | 'com, au, br, ca, fr, de, in, it, jp, mx, nl, pl, sg, sa, es, se, tr, ae, uk' 26 | )); 27 | ?> 28 |
29 |
30 |
31 |
32 | 33 | 34 |
35 |
36 |
37 |
38 | 39 | 40 |
41 |
42 |
43 |
44 |
45 |
46 | 47 | 50 |
51 |
52 |
53 |
54 | 55 | 58 |
59 |
60 |
61 |
62 | 63 |
64 |
65 | 66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | -------------------------------------------------------------------------------- /includes/views/css/chosen-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/includes/views/css/chosen-sprite.png -------------------------------------------------------------------------------- /includes/views/css/chosen-sprite@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codeinwp/feedzy-rss-feeds/979cdf50d7bfb1993c85e8964b14706aca7b0ec3/includes/views/css/chosen-sprite@2x.png -------------------------------------------------------------------------------- /includes/views/css/import-metabox-edit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * import-metabox-edit.css 3 | * 4 | * @since 1.2.0 5 | * @package feedzy-rss-feeds-pro 6 | */ 7 | 8 | .feedzy-toggle { 9 | visibility: hidden; 10 | position: absolute; 11 | margin-left: -9999px; 12 | } 13 | 14 | .feedzy-toggle + label { 15 | display: block; 16 | position: relative; 17 | outline: none; 18 | cursor: pointer; 19 | -webkit-user-select: none; 20 | -moz-user-select: none; 21 | -ms-user-select: none; 22 | user-select: none; 23 | } 24 | 25 | input.feedzy-toggle-round + label { 26 | width: 2rem; 27 | height: 1rem; 28 | border: 1px solid #5555559c; 29 | border-radius: 22px; 30 | background-color: #f1f1f1; 31 | } 32 | 33 | input.feedzy-toggle-round + label:before, 34 | input.feedzy-toggle-round + label:after { 35 | display: block; 36 | position: absolute; 37 | top: 1px; 38 | bottom: 1px; 39 | left: 1px; 40 | content: ""; 41 | } 42 | 43 | input.feedzy-toggle-round + label:before { 44 | right: 1px; 45 | border-radius: 15px; 46 | background-color: #f1f1f1; 47 | -webkit-transition: background 0.4s; 48 | transition: background 0.4s; 49 | } 50 | 51 | input.feedzy-toggle-round + label:after { 52 | width: 0.75rem; 53 | height: 0.75rem; 54 | border-radius: 100%; 55 | background-color: #f6f7f7; 56 | -webkit-transition: margin 0.4s; 57 | transition: margin 0.4s; 58 | } 59 | 60 | input.feedzy-toggle-round:checked + label:before { 61 | background-color:#2271b1; 62 | } 63 | 64 | input.feedzy-toggle-round:checked + label:after { 65 | margin-left: 16px; 66 | background: #f1f1f1; 67 | } 68 | input.feedzy-toggle-round:checked + label{ 69 | border-color: #f6f7f7; 70 | } 71 | .feedzy-dialog:not(.feedzy-dialog-content){ 72 | display:none; 73 | } 74 | 75 | input.feedzy-toggle-round + label:before, 76 | input.feedzy-toggle-round + label:after { 77 | display: block; 78 | position: absolute; 79 | top: 1px; 80 | bottom: 1px; 81 | left: 1px; 82 | content: ""; 83 | } 84 | 85 | input.feedzy-toggle-round + label:before { 86 | right: 1px; 87 | border-radius: 15px; 88 | background-color: #f1f1f1; 89 | -webkit-transition: background 0.4s; 90 | transition: background 0.4s; 91 | } 92 | 93 | input.feedzy-toggle-round + label:after { 94 | border-radius: 100%; 95 | background-color:#5555559c; 96 | -webkit-transition: margin 0.4s; 97 | transition: margin 0.4s; 98 | margin-top:1px; 99 | } 100 | span.feedzy-spinner:not(.is-active) { 101 | display: none; 102 | } 103 | span.feedzy-spinner { 104 | float: none !important; 105 | } 106 | #TB_ajaxContent ul { 107 | list-style: decimal; 108 | margin-left: 20px; 109 | } 110 | 111 | #TB_ajaxContent ul li { 112 | padding: 5px !important; 113 | } 114 | 115 | #TB_ajaxContent p.loading-img { 116 | text-align: center; 117 | } 118 | 119 | #TB_ajaxContent.loaded p.hide-when-loaded { 120 | display: none; 121 | } 122 | 123 | #TB_ajaxContent div.dry_run span { 124 | display: block; 125 | } 126 | 127 | #TB_ajaxContent div.dry_run span i.pass { 128 | color: #149714; 129 | } 130 | 131 | #TB_ajaxContent div.dry_run span i.fail { 132 | color: #ca4a1f; 133 | } 134 | 135 | .feedzy-errors-dialog + .ui-widget-content .ui-dialog-buttonset { 136 | width: 100%; 137 | } 138 | .feedzy-errors-dialog + .ui-widget-content button.feedzy-clear-logs { 139 | margin-left: 0; 140 | } 141 | 142 | .wp-core-ui .fz-export-import-btn, 143 | .wp-core-ui .feedzy-import-limit{ 144 | display: inline-flex !important; 145 | align-items: center; 146 | } 147 | 148 | .wp-core-ui .fz-header-action { 149 | display: inline-flex !important; 150 | gap: 8px; 151 | } 152 | 153 | #fz_import_export_upsell .modal-content { 154 | background: #fff; 155 | border-radius: 3px;] 156 | width: auto; 157 | margin: 1.75rem auto ; 158 | } 159 | #fz_import_export_upsell .modal-body { 160 | text-align: center; 161 | } 162 | #fz_import_export_upsell .modal-header { 163 | padding-bottom: 10px; 164 | margin-bottom: 10px; 165 | position: relative; 166 | } 167 | #fz_import_export_upsell .modal-header .dashicons { 168 | font-size: 1.3em; 169 | line-height: inherit; 170 | } 171 | #fz_import_export_upsell .modal-header h2 { 172 | text-align: center; 173 | } 174 | #fz_import_export_upsell .close-modal { 175 | position: absolute; 176 | top: 0; 177 | right: 0; 178 | } 179 | #fz_import_export_upsell .modal-footer .dashicons{ 180 | 181 | vertical-align: middle; 182 | font-size: initial; 183 | } 184 | #fz_import_export_upsell .modal-footer { 185 | padding-top: 10px; 186 | margin-top: 10px; 187 | text-align: center; 188 | } 189 | .fz-import-field { 190 | background-color: #fff; 191 | max-width: 400px; 192 | width: 100%; 193 | margin: 0 auto; 194 | position: relative; 195 | padding: 15px; 196 | height: 120px; 197 | display: flex; 198 | align-items: center; 199 | justify-content: center; 200 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); 201 | } 202 | .fz-import-field.hidden { 203 | display: none; 204 | } 205 | -------------------------------------------------------------------------------- /includes/views/misc-view.php: -------------------------------------------------------------------------------- 1 |

2 |
3 |
4 | free_settings['canonical'] ) ? $this->free_settings['canonical'] : 0; ?> 5 |
6 |
7 | /> 9 | 10 |
11 |
12 | ', 18 | '' 19 | ) 20 | ); 21 | ?> 22 |
23 |
24 |
25 |
26 |
27 |
28 | free_settings['general']['rss-feeds'] ) && 1 === intval( $this->free_settings['general']['rss-feeds'] ) ) { 31 | $disble_featured_image = 'checked'; 32 | } 33 | ?> 34 | /> 36 | 37 |
38 |
39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /includes/views/openai-view.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | ', '' ); 10 | echo wp_kses_post( $content ); 11 | ?> 12 |
13 |
14 |
15 | 16 |
17 | %2$s', 'feedzy-rss-feeds' ), esc_url( 'https://platform.openai.com/account/api-keys' ), __( 'OpenAI API keys', 'feedzy-rss-feeds' ) ) ); 20 | ?> 21 |
22 | 23 |
24 |
25 | 26 |
27 | %2$s', 'feedzy-rss-feeds' ), 32 | esc_url( 'https://openai.com/api/pricing/' ), 33 | __( 'Pricing', 'feedzy-rss-feeds' ) 34 | ) 35 | ); 36 | ?> 37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | -------------------------------------------------------------------------------- /includes/views/openrouter-view.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | ', '' ); 10 | echo wp_kses_post( $content ); 11 | ?> 12 |
13 |
14 |
15 | 16 |
17 | %2$s', 'feedzy-rss-feeds' ), esc_url( 'https://openrouter.ai/keys' ), __( 'OpenRouter API keys', 'feedzy-rss-feeds' ) ) ); 20 | ?> 21 |
22 | 23 |
24 |
25 | 26 |
27 | %2$s', 'feedzy-rss-feeds' ), esc_url( 'https://openrouter.ai/models' ), __( 'Pricing', 'feedzy-rss-feeds' ) ) ); 30 | ?> 31 |
32 |
33 |
34 | 35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /includes/views/spinnerchief-view.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | ', 10 | '' 11 | ) 12 | ); 13 | ?> 14 |
15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /includes/views/wordai-view.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | ', '' ); 10 | 11 | echo wp_kses_post( $content ); 12 | ?> 13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | { 8 | if ( props.data && props.data.length === 0 ) { 9 | return( 10 | <> 11 | ); 12 | } 13 | 14 | return ( 15 | <> 16 | 17 |
    18 | {props.data.map((value, index) => '' !== value.id && ( 19 | 20 | ))} 21 |
22 |
23 | 24 | ); 25 | }; 26 | export default SortableContainer(Actions); -------------------------------------------------------------------------------- /js/Conditions/DateTimeControl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies. 3 | */ 4 | import { __ } from '@wordpress/i18n'; 5 | 6 | import { 7 | BaseControl, 8 | Button, 9 | DateTimePicker, 10 | Dropdown, 11 | } from '@wordpress/components'; 12 | 13 | // eslint-disable-next-line @wordpress/no-unsafe-wp-apis 14 | import { format, __experimentalGetSettings } from '@wordpress/date'; 15 | 16 | const DateTimeControl = ({ index, label, value, onChange }) => { 17 | const settings = __experimentalGetSettings(); 18 | 19 | return ( 20 | 21 | ( 24 | <> 25 | 34 | 35 | )} 36 | renderContent={() => ( 37 | 38 | )} 39 | /> 40 | 41 | ); 42 | }; 43 | 44 | export default DateTimeControl; 45 | -------------------------------------------------------------------------------- /js/Conditions/PanelTab.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies. 3 | */ 4 | import { __ } from '@wordpress/i18n'; 5 | 6 | import { Button } from '@wordpress/components'; 7 | 8 | import { useState } from '@wordpress/element'; 9 | 10 | const PanelTab = ({ label, onDelete, initialOpen = false, children }) => { 11 | const [isOpen, setOpen] = useState(initialOpen); 12 | 13 | return ( 14 |
15 |
16 |
setOpen(!isOpen)} 19 | > 20 | {label} 21 |
22 | 23 |
41 | 42 | {isOpen &&
{children}
} 43 |
44 | ); 45 | }; 46 | 47 | export default PanelTab; 48 | -------------------------------------------------------------------------------- /js/Conditions/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies. 3 | */ 4 | import domReady from '@wordpress/dom-ready'; 5 | 6 | import { createRoot, useEffect, useState } from '@wordpress/element'; 7 | 8 | /** 9 | * Internal dependencies. 10 | */ 11 | import ConditionsControl from './ConditionsControl'; 12 | 13 | const dummyConditions = { 14 | match: 'all', 15 | conditions: [ 16 | { 17 | field: 'title', 18 | operator: 'contains', 19 | value: 'Sports', 20 | }, 21 | ], 22 | }; 23 | 24 | const App = () => { 25 | const [conditions, setConditions] = useState({ 26 | conditions: [], 27 | match: 'all', 28 | }); 29 | 30 | useEffect(() => { 31 | if (!feedzyData.isPro) { 32 | setConditions(dummyConditions); 33 | return; 34 | } 35 | 36 | const field = document.getElementById('feed-post-filters-conditions'); 37 | if (field && field.value) { 38 | const parsedConditions = JSON.parse(field.value); 39 | setConditions( 40 | parsedConditions && parsedConditions.conditions 41 | ? parsedConditions 42 | : { conditions: [], match: 'all' } 43 | ); 44 | } 45 | }, []); 46 | 47 | useEffect(() => { 48 | if (!feedzyData.isPro) { 49 | return; 50 | } 51 | 52 | document.getElementById('feed-post-filters-conditions').value = 53 | JSON.stringify(conditions); 54 | }, [conditions]); 55 | 56 | return ( 57 | 61 | ); 62 | }; 63 | 64 | domReady(() => { 65 | const root = createRoot(document.getElementById('fz-conditions')); 66 | root.render(); 67 | }); 68 | -------------------------------------------------------------------------------- /js/FeedBack/feedback-form.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import classnames from 'classnames'; 5 | 6 | /** 7 | * WordPress dependencies 8 | */ 9 | import { 10 | useEffect, 11 | useState 12 | } from '@wordpress/element'; 13 | 14 | import { 15 | Button, 16 | Spinner, 17 | TextareaControl 18 | } from '@wordpress/components'; 19 | 20 | import { __ } from '@wordpress/i18n'; 21 | 22 | const { pluginVersion } = window.feedzyObj || {}; 23 | 24 | const collectedInfo = [ 25 | { 26 | name: __( 'Plugin version', 'feedzy-rss-feeds' ), 27 | value: pluginVersion 28 | }, 29 | { 30 | name: __( 'Feedback', 'feedzy-rss-feeds' ), 31 | value: __( 'Text from the above text area', 'feedzy-rss-feeds' ) 32 | } 33 | ]; 34 | 35 | const helpTextByStatus = { 36 | error: __( 'There has been an error. Your feedback couldn\'t be sent.', 'feedzy-rss-feeds' ), 37 | emptyFeedback: __( 'Please provide a feedback before submitting the form.', 'feedzy-rss-feeds' ) 38 | }; 39 | 40 | /** 41 | * Displays a button that opens a modal for sending feedback 42 | * 43 | * @param {import('./type').FeedbackFormProps} props 44 | * @return 45 | */ 46 | const FeedbackForm = ({ 47 | source, 48 | status, 49 | setStatus 50 | }) => { 51 | const [ feedback, setFeedback ] = useState( '' ); 52 | const [ showInfo, setShowInfo ] = useState( false ); 53 | 54 | useEffect( () => { 55 | const info = document.querySelector( '.fz-feedback-form .info' ); 56 | if ( info ) { 57 | info.style.height = showInfo ? `${ info.querySelector( '.wrapper' )?.clientHeight }px` : '0'; 58 | } 59 | 60 | }, [ showInfo ]); 61 | 62 | const sendFeedback = () => { 63 | const trimmedFeedback = feedback.trim(); 64 | if ( 5 >= trimmedFeedback.length ) { 65 | setStatus( 'emptyFeedback' ); 66 | return; 67 | } 68 | 69 | setStatus( 'loading' ); 70 | try { 71 | fetch( 'https://api.themeisle.com/tracking/feedback', { 72 | method: 'POST', 73 | headers: { 74 | 'Content-Type': 'application/json', 75 | 'Accept': 'application/json, */*;q=0.1', 76 | 'Cache-Control': 'no-cache' 77 | }, 78 | body: JSON.stringify({ 79 | slug: 'feedzy-rss-feeds', 80 | version: pluginVersion, 81 | feedback: trimmedFeedback, 82 | data: { 83 | 'feedback-area': source 84 | } 85 | }) 86 | }).then( r => { 87 | if ( ! r.ok ) { 88 | setStatus( 'error' ); 89 | return; 90 | } 91 | 92 | setStatus( 'submitted' ); 93 | })?.catch( ( error ) => { 94 | console.warn( error.message ); 95 | setStatus( 'error' ); 96 | }); 97 | } catch ( error ) { 98 | console.warn( error.message ); 99 | setStatus( 'error' ); 100 | } 101 | }; 102 | 103 | return ( 104 |
{ 107 | e.preventDefault(); 108 | sendFeedback(); 109 | } } 110 | > 111 | { 121 | setFeedback( value ); 122 | if ( 5 < value.trim().length ) { 123 | setStatus( 'notSubmitted' ); 124 | } 125 | } } 126 | help={ helpTextByStatus[status] || false } 127 | autoFocus 128 | /> 129 |
130 |
131 |

{ __( 'We value privacy, that\'s why no domain name, email address or IP addresses are collected after you submit the survey. Below is a detailed view of all data that Themeisle will receive if you fill in this survey.', 'feedzy-rss-feeds' ) }

132 | { collectedInfo.map( ( row, index ) => { 133 | return ( 134 |
135 |

{ row.name }

136 |

{ row.value }

137 |
138 | ); 139 | }) } 140 |
141 |
142 |
143 | 152 | 161 |
162 | 163 | ); 164 | }; 165 | 166 | export default FeedbackForm; -------------------------------------------------------------------------------- /js/FeedBack/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | /** 5 | * External dependencies 6 | */ 7 | import classnames from 'classnames'; 8 | 9 | /** 10 | * WordPress dependencies 11 | */ 12 | import { 13 | Fragment, 14 | useState 15 | } from '@wordpress/element'; 16 | 17 | import { 18 | Modal 19 | } from '@wordpress/components'; 20 | 21 | import { __ } from '@wordpress/i18n'; 22 | 23 | /** 24 | * Internal dependencies 25 | */ 26 | import FeedbackForm from './feedback-form'; 27 | 28 | const finishIcon = `${ window.feedzyObj.assetsUrl }img/finish-feedback.svg`; 29 | 30 | const FeedBack = () => { 31 | const [ isOpen, setOpen ] = useState( false ); 32 | const [ status, setStatus ] = useState( 'notSubmitted' ); 33 | const closeModal = () => setOpen( false ); 34 | 35 | document.querySelectorAll( '[role="button"]' ).forEach((button) => { 36 | button.addEventListener('keydown', (evt) => { 37 | if(evt.keyCode === 13 || evt.keyCode === 32) { 38 | button.click(); 39 | } 40 | }); 41 | }); 42 | document.querySelector( '#fz-feedback-btn' ).addEventListener( 'click', () => setOpen( true ) ); 43 | 44 | return ( 45 | 46 | { isOpen && ( 47 | 55 | { 'submitted' !== status ? ( 56 | 61 | ) : ( 62 |
63 | 66 |

{ __( 'Thank you for your feedback', 'feedzy-rss-feeds' ) }

67 |

{ __( 'Your feedback is highly appreciated and will help us to improve Feedzy RSS Feeds.', 'feedzy-rss-feeds' ) }

68 |
69 | ) } 70 |
71 | ) } 72 |
73 | ); 74 | }; 75 | 76 | ReactDOM.render( 77 | , 78 | document.querySelector('#fz-feedback-modal') 79 | ); -------------------------------------------------------------------------------- /js/FeedzyBlock/attributes.js: -------------------------------------------------------------------------------- 1 | // jshint ignore: start 2 | 3 | const attributes = { 4 | feeds: { 5 | type: 'string', 6 | }, 7 | max: { 8 | type: 'number', 9 | default: 5, 10 | }, 11 | offset: { 12 | type: 'number', 13 | default: 0, 14 | }, 15 | feed_title: { 16 | type: 'boolean', 17 | default: true, 18 | }, 19 | refresh: { 20 | type: 'string', 21 | default: '12_hours', 22 | }, 23 | sort: { 24 | type: 'string', 25 | default: 'default', 26 | }, 27 | target: { 28 | type: 'string', 29 | default: '_blank', 30 | }, 31 | title: { 32 | type: 'number', 33 | }, 34 | meta: { 35 | type: 'boolean', 36 | default: true, 37 | }, 38 | lazy: { 39 | type: 'boolean', 40 | default: false, 41 | }, 42 | metafields: { 43 | type: 'string', 44 | default: '', 45 | }, 46 | multiple_meta: { 47 | type: 'string', 48 | default: '', 49 | }, 50 | summary: { 51 | type: 'boolean', 52 | default: true, 53 | }, 54 | summarylength: { 55 | type: 'number', 56 | }, 57 | keywords_title: { 58 | type: 'string', 59 | }, 60 | keywords_inc_on: { 61 | type: 'string', 62 | default: 'title', 63 | }, 64 | keywords_ban: { 65 | type: 'string', 66 | }, 67 | keywords_exc_on: { 68 | type: 'string', 69 | default: 'title', 70 | }, 71 | thumb: { 72 | type: 'string', 73 | default: 'auto', 74 | }, 75 | default: { 76 | type: 'object', 77 | }, 78 | size: { 79 | type: 'number', 80 | default: 150, 81 | }, 82 | http: { 83 | type: 'string', 84 | }, 85 | referral_url: { 86 | type: 'string', 87 | }, 88 | columns: { 89 | type: 'number', 90 | default: 1, 91 | }, 92 | template: { 93 | type: 'string', 94 | default: 'default', 95 | }, 96 | price: { 97 | type: 'boolean', 98 | default: true, 99 | }, 100 | route: { 101 | type: 'string', 102 | default: 'home', 103 | }, 104 | feedData: { 105 | type: 'object', 106 | }, 107 | categories: { 108 | type: 'object', 109 | }, 110 | from_datetime: { 111 | type: 'string', 112 | }, 113 | to_datetime: { 114 | type: 'string', 115 | }, 116 | itemTitle: { 117 | type: 'boolean', 118 | default: true, 119 | }, 120 | disableStyle: { 121 | type: 'boolean', 122 | default: false, 123 | }, 124 | follow: { 125 | type: 'string', 126 | default: 'no', 127 | }, 128 | error_empty: { 129 | type: 'string', 130 | default: '', 131 | }, 132 | className: { 133 | type: 'string', 134 | default: '', 135 | }, 136 | _dryrun_: { 137 | type: 'string', 138 | default: 'no', 139 | }, 140 | _dry_run_tags_: { 141 | type: 'string', 142 | default: '', 143 | } 144 | }; 145 | 146 | export default attributes; -------------------------------------------------------------------------------- /js/FeedzyBlock/index.js: -------------------------------------------------------------------------------- 1 | // jshint ignore: start 2 | import { __ } from '@wordpress/i18n'; 3 | import { registerBlockType } from '@wordpress/blocks'; 4 | 5 | /** 6 | * Block dependencies 7 | */ 8 | import './style.scss'; 9 | import blockAttributes from './attributes.js'; 10 | import Editor from './Editor.js'; 11 | 12 | /** 13 | * Register block 14 | */ 15 | export default registerBlockType('feedzy-rss-feeds/feedzy-block', { 16 | title: __('Feedzy RSS Feeds (Classic)', 'feedzy-rss-feeds'), 17 | category: 'common', 18 | icon: 'rss', 19 | keywords: [ 20 | __('Feedzy RSS Feeds', 'feedzy-rss-feeds'), 21 | __('RSS', 'feedzy-rss-feeds'), 22 | __('Feeds', 'feedzy-rss-feeds'), 23 | ], 24 | supports: { 25 | html: false, 26 | }, 27 | attributes: blockAttributes, 28 | edit: Editor, 29 | save() { 30 | // Rendering in PHP 31 | return null; 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /js/FeedzyBlock/radio-image-control/index.js: -------------------------------------------------------------------------------- 1 | // jshint ignore: start 2 | 3 | /** 4 | * Block dependencies 5 | */ 6 | import './style.scss'; 7 | import { isEmpty } from 'lodash'; 8 | import { BaseControl } from '@wordpress/components'; 9 | import { withInstanceId } from '@wordpress/compose'; 10 | 11 | function RadioImageControl({ 12 | label, 13 | selected, 14 | help, 15 | instanceId, 16 | onChange, 17 | disabled, 18 | options = [], 19 | }) { 20 | const id = `inspector-radio-image-control-${instanceId}`; 21 | const onChangeValue = (event) => onChange(event.target.value); 22 | 23 | return ( 24 | !isEmpty(options) && ( 25 | 31 |
32 | {options.map((option, index) => ( 33 |
37 | 50 | 57 | {option.label} 58 |
59 | ))} 60 |
61 |
62 | ) 63 | ); 64 | } 65 | 66 | export default withInstanceId(RadioImageControl); 67 | -------------------------------------------------------------------------------- /js/FeedzyBlock/radio-image-control/style.scss: -------------------------------------------------------------------------------- 1 | .components-radio-image-control__container { 2 | display: block; 3 | } 4 | 5 | .components-radio-image-control__option { 6 | display: inline-block; 7 | padding: 5px; 8 | } 9 | 10 | .components-radio-image-control { 11 | 12 | label { 13 | display: inline-block; 14 | position: relative; 15 | 16 | img { 17 | border: 1px solid transparent; 18 | max-width: 250px !important; 19 | } 20 | } 21 | 22 | input { 23 | display: none; 24 | } 25 | 26 | input + label { 27 | .image-clickable { 28 | bottom: 0; 29 | height: 100%; 30 | left: 0; 31 | position: absolute; 32 | right: 0; 33 | top: 0; 34 | width: 100%; 35 | } 36 | } 37 | 38 | input:checked + label { 39 | img { 40 | border: 1px solid #3498DB; 41 | -webkit-box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.25); 42 | box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.25); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /js/FeedzyBlock/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-feedzy-rss-feeds-feedzy-block { 2 | .feedzy-source-wrap { 3 | position: relative; 4 | } 5 | .feedzy-source { 6 | margin-right: 10px; 7 | } 8 | .feedzy-source + .dashicons-arrow-down-alt2 { 9 | position: absolute; 10 | right: 12px; 11 | top: 5px; 12 | z-index: 5; 13 | color: #757575; 14 | cursor: pointer; 15 | } 16 | } 17 | 18 | .loadFeed { 19 | margin-bottom: 10px; 20 | } 21 | 22 | .feedzy-blocks-base-control { 23 | padding-bottom: 10px; 24 | 25 | label { 26 | padding-bottom: 10px; 27 | } 28 | 29 | .feedzy_image_upload { 30 | display: block; 31 | margin-bottom: 10px; 32 | } 33 | } 34 | 35 | .feedzy-select-cat { 36 | width: 100%; 37 | 38 | select { 39 | width: auto; 40 | } 41 | } 42 | 43 | .feedzy-rss { 44 | .rss_image { 45 | span.fetched { 46 | display: inline-block; 47 | position: absolute; 48 | width: 100%; 49 | height: 100%; 50 | background-position: 50%; 51 | background-size: cover; 52 | } 53 | } 54 | } 55 | .feedzy-ui-autocomplete { 56 | max-height: 200px; 57 | overflow-y: auto; 58 | overflow-x: hidden; 59 | padding-right: 20px; 60 | } 61 | .fz-section-header-panel { 62 | &.is-opened { 63 | padding: 0; 64 | } 65 | .header-tab { 66 | display: inline-block; 67 | width: calc( 100% / 3 ); 68 | height: auto; 69 | padding: 10px 20px; 70 | text-align: center; 71 | cursor: pointer; 72 | 73 | &.is-selected { 74 | border-bottom: 2px solid #0085ba; 75 | background: #f3f4f5; 76 | } 77 | 78 | &:hover { 79 | &:not(:disabled):not([aria-disabled="true"]):not(.is-secondary):not(.is-primary):not(.is-tertiary):not(.is-link) { 80 | background: #f3f4f5; 81 | box-shadow: none; 82 | } 83 | } 84 | 85 | span { 86 | display: inline-block; 87 | font-size: 12px; 88 | 89 | .dashicon { 90 | display: block; 91 | margin: 0 auto; 92 | font-size: 20px; 93 | } 94 | } 95 | } 96 | } 97 | .block-editor-block-inspector { 98 | .components-base-control { 99 | margin: 24px 0; 100 | } 101 | } 102 | .fz-locked { 103 | cursor: not-allowed; 104 | >div { 105 | &:not(.fz-upsell-notice) { 106 | opacity: 0.4; 107 | pointer-events: none; 108 | } 109 | } 110 | >p { 111 | opacity: 0.4; 112 | pointer-events: none; 113 | } 114 | >.fz-main-label { 115 | opacity: 0.4; 116 | pointer-events: none; 117 | } 118 | } 119 | .fz-pro-label { 120 | background: #4268CF; 121 | display: flex; 122 | flex-direction: row; 123 | justify-content: center; 124 | align-items: center; 125 | padding: 5px 10px; 126 | color: #FFFFFF; 127 | border-radius: 2px; 128 | font-weight: 700; 129 | font-size: 9.152px; 130 | line-height: 10px; 131 | flex: none; 132 | order: 0; 133 | flex-grow: 0; 134 | margin: 0px 10px; 135 | text-transform: uppercase; 136 | } 137 | .fz-upsell-notice { 138 | font-style: italic; 139 | font-weight: 500; 140 | font-size: 12px; 141 | line-height: 18px; 142 | color: #1E1E1E; 143 | } 144 | 145 | .feedzy-template{ 146 | .components-radio-image-control__container{ 147 | display: flex; 148 | flex-wrap: wrap; 149 | margin: 0 -10px; 150 | .components-radio-image-control__option{ 151 | width: 50%; 152 | padding: 0 10px 20px; 153 | label{ 154 | height: 74px; 155 | display: flex; 156 | align-items: center; 157 | justify-content: center; 158 | background: #F3F4F5; 159 | border: 1px solid #E2E4E7; 160 | border-radius: 4px; 161 | &:hover{ 162 | background: #ffffff; 163 | } 164 | } 165 | span{ 166 | display: block; 167 | text-align: center; 168 | padding-top: 6px; 169 | color: #A8A8A8; 170 | font-weight: 500; 171 | font-size: 12px; 172 | line-height: 18px; 173 | } 174 | } 175 | } 176 | &.components-radio-image-control input:checked + label{ 177 | border-color: #0071AE; 178 | box-shadow: 0 0 0 1px #0071AE; 179 | img{ 180 | border: 0; 181 | box-shadow: none; 182 | } 183 | } 184 | } 185 | .fz-upgrade-alert{ 186 | background-color: #F7F7F7; 187 | border-left: 4px solid #4268CF; 188 | padding: 8px 12px; 189 | font-style: italic; 190 | font-size: 13px; 191 | line-height: 15px; 192 | color: #1E1E1E; 193 | position: relative; 194 | a { 195 | color: #4268CF !important; 196 | font-weight: 700; 197 | } 198 | span.dashicons-no-alt{ 199 | position: absolute; 200 | right: 4px; 201 | top: 4px; 202 | color: #6F7882; 203 | cursor: pointer; 204 | } 205 | } 206 | .fz-source-upgrade-alert{ 207 | width: 100%; 208 | background-color: #CCE5FF; 209 | border-left: 4px solid #4268CF; 210 | padding: 15px 5px; 211 | font-style: italic; 212 | font-size: 13px; 213 | line-height: 15px; 214 | color: #264494; 215 | a{ 216 | color: #264494 !important; 217 | font-weight: 700; 218 | } 219 | } 220 | .feedzy-ui-autocomplete li.ui-menu-item { 221 | font-size: 13px; 222 | line-height: 1.4em; 223 | color: #3c434a; 224 | } -------------------------------------------------------------------------------- /js/FeedzyBlock/utils.js: -------------------------------------------------------------------------------- 1 | // jshint ignore: start 2 | 3 | export const unescapeHTML = value => { 4 | const htmlNode = document.createElement( 'div' ); 5 | htmlNode.innerHTML = value; 6 | if( htmlNode.innerText !== undefined ) { 7 | return htmlNode.innerText; 8 | } 9 | return htmlNode.textContent; 10 | }; 11 | 12 | export const filterData = ( arr, sortType, allowedKeywords, bannedKeywords, maxSize, offset, includeOn, excludeOn, fromDateTime, toDateTime ) => { 13 | includeOn = 'author' === includeOn ? 'creator' : includeOn; 14 | excludeOn = 'author' === excludeOn ? 'creator' : excludeOn; 15 | fromDateTime = '' !== fromDateTime && 'undefined' !== typeof fromDateTime ? moment( fromDateTime ).format( 'X' ) : false ; 16 | toDateTime = '' !== toDateTime && 'undefined' !== typeof toDateTime ? moment( toDateTime ).format( 'X' ) : false; 17 | 18 | arr = Array.from( arr ).sort( (a, b) => { 19 | let firstElement, secondElement; 20 | if ( sortType === 'date_desc' || sortType === 'date_asc' ) { 21 | firstElement = a.pubDate; 22 | secondElement = b.pubDate; 23 | } else if ( sortType === 'title_desc' || sortType === 'title_asc' ) { 24 | firstElement = a.title.toUpperCase(); 25 | secondElement = b.title.toUpperCase(); 26 | } 27 | if ( firstElement < secondElement ) { 28 | if ( sortType === 'date_desc' || sortType === 'title_desc' ) { 29 | return 1; 30 | } else { 31 | return -1; 32 | } 33 | } 34 | if ( firstElement > secondElement ) { 35 | if ( sortType === 'date_desc' || sortType === 'title_desc' ) { 36 | return -1; 37 | } else { 38 | return 1; 39 | } 40 | } 41 | // names must be equal 42 | return 0; 43 | }).filter( item => { 44 | if ( allowedKeywords ) { 45 | return allowedKeywords.test( item[ includeOn ] ); 46 | } 47 | return true; 48 | }).filter( item => { 49 | if ( bannedKeywords ) { 50 | return ! bannedKeywords.test( item[ excludeOn ] ); 51 | } 52 | return true; 53 | }).filter( item => { 54 | let itemDateTime = item.date + ' ' + item.time; 55 | itemDateTime = moment( new Date( itemDateTime ) ).format( 'X' ); 56 | if ( fromDateTime && toDateTime ) { 57 | return ( ( fromDateTime <= itemDateTime ) && ( itemDateTime <= toDateTime ) ); 58 | } 59 | return true; 60 | }).slice( offset, maxSize + offset ); 61 | return arr; 62 | }; 63 | 64 | export const inArray = ( value, arr ) => { 65 | if ( arr === undefined ) { 66 | return false; 67 | }; 68 | let exists = false; 69 | for( let i = 0; i < arr.length; i++ ) { 70 | let name = arr[i]; 71 | if ( name === value ) { 72 | exists = true; 73 | break; 74 | } 75 | } 76 | return exists; 77 | }; 78 | 79 | export const arrangeMeta = ( values, fields ) => { 80 | let meta = ''; 81 | 82 | if(fields === ''){ 83 | fields = 'author, date, time'; 84 | } 85 | 86 | let arr = fields.replace(/\s/g,'').split( ',' ); 87 | 88 | for(let i = 0; i < arr.length; i++){ 89 | if(typeof values[ arr[i] ] !== 'undefined'){ 90 | meta = meta + ' ' + values[ arr[i] ]; 91 | } 92 | } 93 | return meta; 94 | }; 95 | 96 | export const filterCustomPattern = ( keyword = '' ) => { 97 | let pattern = ''; 98 | let regex = []; 99 | if ( '' !== keyword && keyword.replace( /[^a-zA-Z]/g, '' ).length <= 500 ) { 100 | keyword 101 | .split( ',' ) 102 | .forEach( item => { 103 | item = item.trim(); 104 | if ( '' !== item ) { 105 | item = item.split( '+' ) 106 | .map( k => { 107 | k = k.trim(); 108 | return '(?=.*' + k + ')'; 109 | } ); 110 | regex.push( item.join( '' ) ); 111 | } 112 | } ); 113 | pattern = regex.join( '|' ); 114 | pattern = '^' + pattern + '.*$'; 115 | pattern = new RegExp( pattern, 'i' ); 116 | } 117 | return pattern; 118 | }; -------------------------------------------------------------------------------- /js/FeedzyLoop/block.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schemas.wp.org/trunk/block.json", 3 | "apiVersion": 3, 4 | "name": "feedzy-rss-feeds/loop", 5 | "version": "1.0.0", 6 | "title": "Feedzy Loop", 7 | "category": "common", 8 | "icon": "rss", 9 | "keywords": [ 10 | "rss", 11 | "feed", 12 | "feedzy" 13 | ], 14 | "description": "Display curated RSS content in a dynamic, customizable loop directly in the Block Editor—no coding required.", 15 | "attributes": { 16 | "feed": { 17 | "type": "object", 18 | "properties": { 19 | "type": { 20 | "type": "string", 21 | "enum": [ "url", "group" ], 22 | "default": "url" 23 | }, 24 | "source": { 25 | "type": [ "number", "array" ], 26 | "default": "" 27 | } 28 | } 29 | }, 30 | "query": { 31 | "type": "object", 32 | "properties": { 33 | "max": { 34 | "type": "number", 35 | "default": 5 36 | }, 37 | "sort": { 38 | "type": "string", 39 | "enum": [ "default", "date_desc", "date_asc", "title_desc", "title_asc" ], 40 | "default": "default" 41 | }, 42 | "refresh": { 43 | "type": "string", 44 | "enum": [ "1_hours", "3_hours", "12_hours", "1_days", "3_days", "15_days" ], 45 | "default": "12_hours" 46 | } 47 | } 48 | }, 49 | "layout": { 50 | "type": "object", 51 | "properties": { 52 | "columnCount": { 53 | "type": "number", 54 | "default": 1 55 | } 56 | } 57 | }, 58 | "conditions": { 59 | "type": "object", 60 | "properties": { 61 | "match": { 62 | "type": "string", 63 | "enum": [ "all", "any" ], 64 | "default": "all" 65 | }, 66 | "conditions": { 67 | "type": "array", 68 | "items": { 69 | "type": "object", 70 | "properties": { 71 | "field": { 72 | "type": "string", 73 | "default": "title" 74 | }, 75 | "operator": { 76 | "type": "string", 77 | "default": "contains" 78 | }, 79 | "value": { 80 | "type": "string", 81 | "default": "" 82 | } 83 | } 84 | } 85 | } 86 | } 87 | }, 88 | "innerBlocksContent": { 89 | "type": "string", 90 | "default": "" 91 | } 92 | }, 93 | "supports": { 94 | "align": [ "wide", "full" ], 95 | "anchor": true, 96 | "ariaLabel": true, 97 | "html": true, 98 | "color": { 99 | "gradients": true, 100 | "heading": true, 101 | "button": true, 102 | "link": true 103 | }, 104 | "shadow": true, 105 | "spacing": { 106 | "margin": [ "top", "bottom" ], 107 | "padding": true, 108 | "blockGap": true 109 | }, 110 | "dimensions": { 111 | "minHeight": true 112 | }, 113 | "typography": { 114 | "fontSize": true, 115 | "lineHeight": true 116 | } 117 | }, 118 | "editorScript": "file:./index.js", 119 | "editorStyle": "file:./index.css", 120 | "style": "file:./style-index.css", 121 | "textdomain": "feedzy-rss-block" 122 | } -------------------------------------------------------------------------------- /js/FeedzyLoop/components/FeedControl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies. 3 | */ 4 | import { __ } from '@wordpress/i18n'; 5 | 6 | import { useState, useEffect, useRef } from '@wordpress/element'; 7 | 8 | const FeedControl = ({ value, options, onChange }) => { 9 | const [isOpen, setIsOpen] = useState(false); 10 | const [inputValue, setInputValue] = useState(''); 11 | const [selectedOption, setSelectedOption] = useState(null); 12 | const dropdownRef = useRef(null); 13 | 14 | // Initialize component state based on value prop 15 | useEffect(() => { 16 | if (value?.type === 'group' && value.source) { 17 | const selected = options.find((opt) => opt.value === value.source); 18 | setSelectedOption(selected || null); 19 | setInputValue(''); 20 | } else if (value?.type === 'url' && Array.isArray(value.source)) { 21 | setSelectedOption(null); 22 | setInputValue(value.source.join(', ')); 23 | } 24 | }, [value, options]); 25 | 26 | useEffect(() => { 27 | const handleClickOutside = (event) => { 28 | if ( 29 | dropdownRef.current && 30 | !dropdownRef.current.contains(event.target) 31 | ) { 32 | setIsOpen(false); 33 | } 34 | }; 35 | 36 | document.addEventListener('mousedown', handleClickOutside); 37 | return () => 38 | document.removeEventListener('mousedown', handleClickOutside); 39 | }, []); 40 | 41 | const handleSelectOption = (option) => { 42 | setSelectedOption(option); 43 | setInputValue(''); 44 | setIsOpen(false); 45 | onChange({ 46 | type: 'group', 47 | source: option.value, 48 | }); 49 | }; 50 | 51 | const handleInputChange = (e) => { 52 | const current = e.target.value; 53 | setInputValue(current); 54 | setSelectedOption(null); 55 | }; 56 | 57 | const handleInputBlur = () => { 58 | onChange({ 59 | type: 'url', 60 | source: inputValue 61 | ? inputValue 62 | .split(',') 63 | .map((url) => url.trim()) 64 | .filter(Boolean) 65 | : [], 66 | }); 67 | }; 68 | 69 | const handleClear = () => { 70 | setSelectedOption(null); 71 | setInputValue(''); 72 | onChange({ 73 | type: 'url', 74 | source: [], 75 | }); 76 | }; 77 | 78 | return ( 79 |
80 | 92 |
93 | {selectedOption && ( 94 | 115 | )} 116 | 143 |
144 | 145 | {isOpen && ( 146 |
147 | {options.map((option) => ( 148 | 155 | ))} 156 |
157 | )} 158 |
159 | ); 160 | }; 161 | 162 | export default FeedControl; 163 | -------------------------------------------------------------------------------- /js/FeedzyLoop/components/PatternSelector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies. 3 | */ 4 | import { __ } from '@wordpress/i18n'; 5 | 6 | import { createBlocksFromInnerBlocksTemplate } from '@wordpress/blocks'; 7 | 8 | import { 9 | store as blockEditorStore, 10 | BlockPreview, 11 | } from '@wordpress/block-editor'; 12 | 13 | import { Modal } from '@wordpress/components'; 14 | 15 | import { useDispatch, useSelect } from '@wordpress/data'; 16 | 17 | /** 18 | * Internal dependencies. 19 | */ 20 | import variations from '../variations'; 21 | 22 | const PatternSelector = ({ setOpen }) => { 23 | const currentBlock = useSelect((select) => 24 | select(blockEditorStore).getSelectedBlock() 25 | ); 26 | 27 | const { clearSelectedBlock, replaceBlock } = useDispatch(blockEditorStore); 28 | 29 | return ( 30 | setOpen(false)} 33 | size="fill" 34 | > 35 |
36 | {variations.map((variation) => { 37 | const block = { 38 | ...currentBlock, 39 | attributes: { 40 | feed: currentBlock?.attributes?.feed, 41 | ...variation?.attributes, 42 | }, 43 | innerBlocks: createBlocksFromInnerBlocksTemplate( 44 | variation?.innerBlocks 45 | ), 46 | }; 47 | 48 | const onClick = () => { 49 | replaceBlock(currentBlock.clientId, block); 50 | clearSelectedBlock(); 51 | setOpen(false); 52 | }; 53 | 54 | return ( 55 |
{ 59 | if (e.key === 'Enter' || e.key === ' ') { 60 | onClick(); 61 | } 62 | }} 63 | role="button" 64 | tabIndex="0" 65 | className="fz-pattern" 66 | > 67 | 68 | 69 |
{variation.title}
70 |
71 | ); 72 | })} 73 |
74 |
75 | ); 76 | }; 77 | 78 | export default PatternSelector; 79 | -------------------------------------------------------------------------------- /js/FeedzyLoop/edit.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */ 2 | /** 3 | * WordPress dependencies. 4 | */ 5 | import { 6 | store as blocksStore, 7 | createBlocksFromInnerBlocksTemplate, 8 | serialize, 9 | } from '@wordpress/blocks'; 10 | 11 | import { 12 | store as blockEditorStore, 13 | __experimentalBlockVariationPicker as BlockVariationPicker, 14 | useBlockProps, 15 | InnerBlocks, 16 | } from '@wordpress/block-editor'; 17 | 18 | import { 19 | Placeholder as BlockEditorPlaceholder, 20 | Spinner, 21 | } from '@wordpress/components'; 22 | 23 | import { useDispatch, useSelect } from '@wordpress/data'; 24 | 25 | import { useState } from '@wordpress/element'; 26 | 27 | import ServerSideRender from '@wordpress/server-side-render'; 28 | 29 | /** 30 | * Internal dependencies. 31 | */ 32 | import metadata from './block.json'; 33 | import Placeholder from './placeholder'; 34 | import Controls from './controls'; 35 | 36 | const { name } = metadata; 37 | 38 | const LoadingResponsePlaceholder = () => ( 39 | 40 | 41 | 42 | ); 43 | 44 | const Edit = ({ attributes, setAttributes, clientId }) => { 45 | const blockProps = useBlockProps(); 46 | 47 | const [isEditing, setIsEditing] = useState(!attributes?.feed?.source); 48 | const [isPreviewing, setIsPreviewing] = useState(false); 49 | 50 | const { clearSelectedBlock, replaceInnerBlocks } = 51 | useDispatch(blockEditorStore); 52 | 53 | const isSelected = useSelect( 54 | (select) => { 55 | const { isBlockSelected, hasSelectedInnerBlock } = 56 | select(blockEditorStore); 57 | return ( 58 | isBlockSelected(clientId) || 59 | hasSelectedInnerBlock(clientId, true) 60 | ); 61 | }, 62 | [clientId] 63 | ); 64 | 65 | const innerBlocksContent = useSelect( 66 | (select) => { 67 | const { getBlock } = select(blockEditorStore); 68 | const block = getBlock(clientId); 69 | 70 | return serialize(block?.innerBlocks) ?? ''; 71 | }, 72 | [clientId] 73 | ); 74 | 75 | const hasInnerBlocks = useSelect( 76 | (select) => 0 < select(blockEditorStore).getBlocks(clientId).length, 77 | [clientId] 78 | ); 79 | 80 | const variations = useSelect((select) => { 81 | const { getBlockVariations } = select(blocksStore); 82 | return getBlockVariations(name, 'block'); 83 | }, []); 84 | 85 | const defaultVariation = useSelect((select) => { 86 | const { getDefaultBlockVariation } = select(blocksStore); 87 | return getDefaultBlockVariation(name, 'block'); 88 | }, []); 89 | 90 | const onSaveFeed = () => { 91 | setIsEditing(false); 92 | }; 93 | 94 | const onChangeQuery = ({ type, value }) => { 95 | setAttributes({ 96 | query: { 97 | ...attributes.query, 98 | [type]: value, 99 | }, 100 | }); 101 | }; 102 | 103 | const onChangeLayout = ({ type, value }) => { 104 | setAttributes({ 105 | layout: { 106 | ...attributes.layout, 107 | [type]: value, 108 | }, 109 | }); 110 | }; 111 | 112 | if (isEditing) { 113 | return ( 114 |
115 | 120 |
121 | ); 122 | } 123 | 124 | if ((!isSelected || isPreviewing) && innerBlocksContent) { 125 | return ( 126 | <> 127 | 137 | 138 |
139 | 147 |
148 | 149 | ); 150 | } 151 | 152 | return ( 153 | <> 154 | 164 | 165 |
166 | {hasInnerBlocks ? ( 167 | 168 | ) : ( 169 | { 172 | if (nextVariation) { 173 | setAttributes(nextVariation.attributes); 174 | replaceInnerBlocks( 175 | clientId, 176 | createBlocksFromInnerBlocksTemplate( 177 | nextVariation.innerBlocks 178 | ), 179 | true 180 | ); 181 | clearSelectedBlock(); 182 | } 183 | }} 184 | /> 185 | )} 186 |
187 | 188 | ); 189 | }; 190 | 191 | export default Edit; 192 | -------------------------------------------------------------------------------- /js/FeedzyLoop/extension.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies. 3 | */ 4 | import { __ } from '@wordpress/i18n'; 5 | 6 | import { 7 | BlockControls, 8 | store as blockEditorStore, 9 | } from '@wordpress/block-editor'; 10 | 11 | import { ToolbarButton, ToolbarGroup } from '@wordpress/components'; 12 | 13 | import { createHigherOrderComponent } from '@wordpress/compose'; 14 | 15 | import { useSelect } from '@wordpress/data'; 16 | 17 | import { addFilter } from '@wordpress/hooks'; 18 | 19 | const defaultImage = window.feedzyData.defaultImage; 20 | 21 | const withFeedzyLoopImage = createHigherOrderComponent((BlockEdit) => { 22 | return (props) => { 23 | if ('core/image' !== props.name) { 24 | return ; 25 | } 26 | 27 | const isLoopChild = useSelect((select) => { 28 | return ( 29 | select(blockEditorStore).getBlockParentsByBlockName( 30 | props.clientId, 31 | 'feedzy-rss-feeds/loop' 32 | ).length > 0 33 | ); 34 | }); 35 | 36 | return ( 37 | <> 38 | 39 | 40 | {isLoopChild && ( 41 | 42 | 43 | { 45 | props.setAttributes({ 46 | url: defaultImage, 47 | }); 48 | }} 49 | > 50 | {__('Use as Feed Image', 'feedzy-rss-feeds')} 51 | 52 | 53 | 54 | )} 55 | 56 | ); 57 | }; 58 | }, 'withMasonryExtension'); 59 | 60 | addFilter('editor.BlockEdit', 'feedzy-loop/image', withFeedzyLoopImage); 61 | -------------------------------------------------------------------------------- /js/FeedzyLoop/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | import { __ } from '@wordpress/i18n'; 5 | 6 | import { createBlock, registerBlockType } from '@wordpress/blocks'; 7 | 8 | import { InnerBlocks } from '@wordpress/block-editor'; 9 | 10 | /** 11 | * Internal dependencies 12 | */ 13 | import './editor.scss'; 14 | import './style.scss'; 15 | import './extension'; 16 | import metadata from './block.json'; 17 | import variations from './variations'; 18 | import edit from './edit'; 19 | 20 | const { name } = metadata; 21 | 22 | registerBlockType(name, { 23 | ...metadata, 24 | variations, 25 | transforms: { 26 | from: [ 27 | { 28 | type: 'block', 29 | blocks: ['core/rss'], 30 | transform: (attributes) => { 31 | const { feedURL } = attributes; 32 | 33 | if (feedURL) { 34 | return createBlock(name, { 35 | feed: { type: 'url', source: [feedURL] }, 36 | }); 37 | } 38 | 39 | return createBlock(name); 40 | }, 41 | }, 42 | { 43 | type: 'block', 44 | blocks: ['feedzy-rss-feeds/feedzy-block'], 45 | transform: (attributes) => { 46 | const { feeds } = attributes; 47 | 48 | if (feeds) { 49 | return createBlock(name, { 50 | feed: { type: 'url', source: [feeds] }, 51 | }); 52 | } 53 | 54 | return createBlock(name); 55 | }, 56 | }, 57 | ], 58 | }, 59 | edit, 60 | save: () => { 61 | return ; 62 | }, 63 | }); 64 | -------------------------------------------------------------------------------- /js/FeedzyLoop/placeholder.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @wordpress/no-unsafe-wp-apis */ 2 | /** 3 | * WordPress dependencies. 4 | */ 5 | import { __ } from '@wordpress/i18n'; 6 | 7 | import { 8 | BaseControl, 9 | Button, 10 | Placeholder, 11 | Spinner, 12 | } from '@wordpress/components'; 13 | 14 | import { useSelect } from '@wordpress/data'; 15 | 16 | import { store as coreStore } from '@wordpress/core-data'; 17 | 18 | /** 19 | * Internal dependencies. 20 | */ 21 | import FeedControl from './components/FeedControl'; 22 | 23 | const BlockPlaceholder = ({ attributes, setAttributes, onSaveFeed }) => { 24 | const { categories, isLoading } = useSelect((select) => { 25 | const { getEntityRecords, isResolving } = select(coreStore); 26 | 27 | return { 28 | categories: getEntityRecords('postType', 'feedzy_categories') ?? [], 29 | isLoading: isResolving('getEntityRecords', [ 30 | 'postType', 31 | 'feedzy_categories', 32 | ]), 33 | }; 34 | }, []); 35 | 36 | return ( 37 | 42 | {isLoading && ( 43 |
44 | 45 |

{__('Fetching…', 'feedzy-rss-feeds')}

46 |
47 | )} 48 | 49 | {!isLoading && ( 50 | <> 51 | 55 | ({ 59 | label: category?.title?.rendered, 60 | value: category.id, 61 | })), 62 | ]} 63 | onChange={(value) => setAttributes({ feed: value })} 64 | /> 65 | 66 |

67 | {__( 68 | 'Enter the full URL of the feed source you wish to display here, or select a Feed Group. Also you can add multiple URLs separated with a comma. You can manage your feed groups from', 69 | 'feedzy-rss-feeds' 70 | )}{' '} 71 | 76 | {__('here', 'feedzy-rss-feeds')} 77 | 78 |

79 |
80 | 81 |
82 | 92 | 93 | 100 |
101 | 102 | )} 103 |
104 | ); 105 | }; 106 | 107 | export default BlockPlaceholder; 108 | -------------------------------------------------------------------------------- /js/FeedzyLoop/style.scss: -------------------------------------------------------------------------------- 1 | .wp-block-feedzy-rss-feeds-loop { 2 | display: grid; 3 | gap: 24px; 4 | 5 | @media (min-width: 782px) { 6 | @for $i from 2 through 5 { 7 | &.feedzy-loop-columns-#{$i} { 8 | grid-template-columns: repeat(2, 1fr); 9 | } 10 | } 11 | } 12 | 13 | @media (min-width: 960px) { 14 | @for $i from 2 through 5 { 15 | &.feedzy-loop-columns-#{$i} { 16 | grid-template-columns: repeat(#{$i}, 1fr); 17 | } 18 | } 19 | } 20 | 21 | grid-template-columns: repeat(1, 1fr); 22 | 23 | .wp-block-image.is-style-rounded img { 24 | border-radius: 9999px; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /js/Review/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies. 3 | */ 4 | import { 5 | __, 6 | sprintf 7 | } from '@wordpress/i18n'; 8 | 9 | import apiFetch from '@wordpress/api-fetch'; 10 | 11 | import domReady from '@wordpress/dom-ready'; 12 | 13 | import { 14 | Button, 15 | Modal 16 | } from '@wordpress/components'; 17 | 18 | import { 19 | createRoot, 20 | useState 21 | } from '@wordpress/element'; 22 | 23 | const App = () => { 24 | const [ isOpen, setOpen ] = useState( true ); 25 | 26 | const closeModal = async () => { 27 | try { 28 | await apiFetch({ 29 | path: '/wp/v2/settings', 30 | method: 'POST', 31 | data: { 32 | feedzy_review_notice: 'yes', 33 | }, 34 | }); 35 | } catch ( error ) { 36 | console.error( __( 'Error updating setting:', 'feedzy-rss-feeds' ), error ); 37 | } finally { 38 | setOpen( false ); 39 | } 40 | }; 41 | 42 | if ( ! isOpen ) { 43 | return null; 44 | } 45 | 46 | return ( 47 | 53 |

', // %1$s 62 | '', // %2$s 63 | 100 // %3$s 64 | ), 65 | }} 66 | /> 67 | 68 |

', // %1$s 77 | '', // %2$s 78 | '★★★★★' // %3$s 79 | ), 80 | }} 81 | /> 82 | 83 |

84 | 93 |
94 |
95 | ); 96 | }; 97 | 98 | domReady( () => { 99 | const modalContainer = document.createElement( 'div' ); 100 | modalContainer.id = 'fz-review-modal'; 101 | document.body.appendChild( modalContainer ); 102 | const root = createRoot( document.getElementById( 'fz-review-modal' ) ); 103 | root.render( ); 104 | }); 105 | -------------------------------------------------------------------------------- /js/categories.js: -------------------------------------------------------------------------------- 1 | /* global feedzy, ajaxurl */ 2 | /* jshint unused:false */ 3 | (function($) { 4 | 5 | $(document).ready(function(){ 6 | init(); 7 | }); 8 | 9 | function init(){ 10 | // listen to the validate button 11 | $('button.validate-category').on('click', function(e){ 12 | e.preventDefault(); 13 | var button = $(this); 14 | button.html(feedzy.l10n.validating); 15 | $.ajax({ 16 | url: ajaxurl, 17 | method: 'POST', 18 | data: { 19 | action: 'feedzy_categories', 20 | _action: 'validate_clean', 21 | security: feedzy.ajax.security, 22 | id: button.attr('data-category-id') 23 | }, 24 | success: function(data){ 25 | button.html(feedzy.l10n.validated.replace('#', data.data.invalid)); 26 | }, 27 | complete: function(){ 28 | setTimeout(function(){ 29 | button.html(feedzy.l10n.validate); 30 | }, 5000 ); 31 | } 32 | }); 33 | }); 34 | } 35 | 36 | })(jQuery, feedzy); 37 | -------------------------------------------------------------------------------- /js/feedzy-elementor-widget.js: -------------------------------------------------------------------------------- 1 | window.addEventListener( 'elementor/init', function() { 2 | 3 | /** 4 | * Handle select layout template 5 | */ 6 | var fzLayoutTemplate = elementor.modules.controls.BaseData.extend({ 7 | onReady() { 8 | var self = this; 9 | this.template_control = this.$el.find('.fz-layout-choices input[type="radio"]'); 10 | this.template_control.change( function() { 11 | self.saveValue( jQuery( this ).val() ); 12 | } ); 13 | self.changeUiImage(); 14 | }, 15 | saveValue(value) { 16 | this.setValue(value); 17 | }, 18 | changeUiImage() { // Set UI theme mode. 19 | var themeMode = elementor.settings.editorPreferences.model.get('ui_theme'); 20 | var userPrefersDark = matchMedia('(prefers-color-scheme: dark)').matches; 21 | 22 | if ( 'dark' === themeMode || userPrefersDark ) { 23 | this.$el.removeClass( 'fz-el-light-mode' ).addClass( 'fz-el-dark-mode' ); 24 | } else { 25 | this.$el.addClass( 'fz-el-light-mode' ).removeClass( 'fz-el-dark-mode' ); 26 | } 27 | 28 | this.$el.find( '.img img' ).each( function() { 29 | if ( 'dark' === themeMode ) { 30 | jQuery( this ).attr( 'src', jQuery( this ).attr( 'src' ).replace( '{{ui_mode}}', 'dark' ) ); 31 | } else { 32 | jQuery( this ).attr( 'src', jQuery( this ).attr( 'src' ).replace( '{{ui_mode}}', 'light' ) ); 33 | } 34 | } ); 35 | } 36 | }); 37 | elementor.addControlView( 'fz-layout-template', fzLayoutTemplate ); 38 | 39 | // Edit widget event. 40 | elementor.hooks.addAction( 'panel/open_editor/widget/feedzy-rss-feeds', function( panel, model, view ) { 41 | var themeMode = elementor.settings.editorPreferences.model.get('ui_theme'); 42 | var userPrefersDark = matchMedia('(prefers-color-scheme: dark)').matches; 43 | 44 | if ( FeedzyElementorEditor.notice ) { 45 | if ( jQuery('.fz-pro-notice').length <= 0 && jQuery('.elementor-control-fz-referral-url').length > 0 ) { 46 | // Append notice. 47 | jQuery( FeedzyElementorEditor.notice ).insertAfter( jQuery('.elementor-control-fz-referral-url').parents('div.elementor-controls-stack') ); 48 | // Set UI theme mode. 49 | var fzLogo = jQuery('.fz-pro-notice .fz-logo img'); 50 | if ( 'dark' === themeMode || userPrefersDark ) { 51 | fzLogo.attr( 'src', fzLogo.attr( 'src' ).replace( '{{ui_mode}}', 'dark' ) ); 52 | jQuery('.fz-pro-notice').removeClass( 'fz-light-mode' ); 53 | } else { 54 | jQuery('.fz-pro-notice').addClass( 'fz-light-mode' ); 55 | fzLogo.attr( 'src', fzLogo.attr( 'src' ).replace( '{{ui_mode}}', 'light' ) ); 56 | } 57 | } 58 | } 59 | 60 | if( '' !== FeedzyElementorEditor.upsell_notice ) { 61 | if ( 'dark' === themeMode || userPrefersDark ) { 62 | jQuery( FeedzyElementorEditor.upsell_notice ).addClass( 'dark-mode' ).removeClass( 'light-mode' ).insertAfter( '.elementor-panel-navigation' ); 63 | } else { 64 | jQuery( FeedzyElementorEditor.upsell_notice ).addClass( 'light-mode' ).removeClass( 'dark-mode' ).insertAfter( '.elementor-panel-navigation' ); 65 | } 66 | 67 | jQuery( document ).on( 'click', '.remove-alert', function() { 68 | var upSellNotice = jQuery(this).parents( '.fz-upsell-notice' ); 69 | upSellNotice.fadeOut( 500, 70 | function() { 71 | upSellNotice.remove(); 72 | jQuery.post( 73 | ajaxurl, 74 | { 75 | security: FeedzyElementorEditor.security, 76 | action: "feedzy", 77 | _action: "remove_upsell_notice" 78 | } 79 | ); 80 | } 81 | ); 82 | } ); 83 | } 84 | } ); 85 | 86 | var proTitleText = function() { 87 | if ( jQuery( '.fz-feat-locked:not(.elementor-control-type-section)' ).length > 0 ) { 88 | jQuery( '.fz-feat-locked:not(.elementor-control-type-section)' ).attr( 'title', FeedzyElementorEditor.pro_title_text ); 89 | } 90 | }; 91 | 92 | elementor.channels.editor.on( 'section:activated', function() { 93 | proTitleText(); 94 | } ); 95 | } ); 96 | -------------------------------------------------------------------------------- /js/feedzy-lazy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Plugin Name: FEEDZY RSS Feeds 3 | * Plugin URI: https://themeisle.com/plugins/feedzy-rss-feeds/ 4 | * Author: Themeisle 5 | * 6 | * @package feedzy-rss-feeds 7 | */ 8 | /* global feedzy */ 9 | /* jshint unused:false */ 10 | (function($) { 11 | 12 | // load all attributes into the ajax call. 13 | $('.feedzy-lazy:not(.loading)').each(function() { 14 | var $feedzy_block = $(this); 15 | var $attributes = {}; 16 | $.each(this.attributes, function() { 17 | if(this.specified && this.name.includes('data-')) { 18 | $attributes[this.name.replace('data-', '')] = this.value; 19 | } 20 | }); 21 | 22 | if ( 'true' === $attributes.has_valid_cache ) { 23 | return; 24 | } 25 | delete $attributes.has_valid_cache; 26 | setTimeout( function(){ 27 | $feedzy_block.addClass('loading'); 28 | $.ajax({ 29 | url: feedzy.url, 30 | method: 'POST', 31 | data: { 32 | action: 'feedzy', 33 | _action: 'lazy', 34 | args: $attributes, 35 | nonce: feedzy.nonce 36 | }, 37 | beforeSend: function (xhr) { 38 | xhr.setRequestHeader('X-WP-Nonce', feedzy.rest_nonce); 39 | }, 40 | success: function(data){ 41 | if(data.success){ 42 | $feedzy_block.empty().append(data.data.content); 43 | } 44 | }, 45 | complete: function(){ 46 | $feedzy_block.removeClass('loading'); 47 | } 48 | }); 49 | }, 1000 ); 50 | }); 51 | })(jQuery, feedzy); 52 | -------------------------------------------------------------------------------- /js/feedzy-rss-feeds-ui-mce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Plugin Name: FEEDZY RSS Feeds 3 | * Plugin URI: https://themeisle.com/plugins/feedzy-rss-feeds/ 4 | * Author: Themeisle 5 | * 6 | * @package feedzy-rss-feeds 7 | */ 8 | /* global tinymce */ 9 | /* jshint unused:false */ 10 | (function($) { 11 | tinymce.PluginManager.add('feedzy_mce_button', function( editor, url ) { 12 | editor.addButton( 'feedzy_mce_button', { 13 | title: getTranslation( editor, 'plugin_label' ), 14 | label: getTranslation( editor, 'plugin_label' ), 15 | icon: 'feedzy-icon', 16 | onclick: function() { 17 | editor.windowManager.open( { 18 | title: getTranslation( editor, 'plugin_title' ), 19 | url: getTranslation( editor, 'popup_url' ) + '&action=get_tinymce_form', 20 | width: $( window ).width() * 0.7, 21 | height: ($( window ).height() - 36 - 50) * 0.7, 22 | inline: 1, 23 | id: 'feedzy-rss-insert-dialog', 24 | buttons: [{ 25 | text: getTranslation( editor, 'pro_button' ), 26 | id: 'feedzy-rss-button-pro', 27 | onclick: function( e ) { 28 | openProLink( e, editor ); 29 | }, 30 | }, 31 | { 32 | text: getTranslation( editor, 'cancel_button' ), 33 | id: 'feedzy-rss-button-cancel', 34 | onclick: 'close' 35 | }, 36 | { 37 | text: getTranslation( editor, 'insert_button' ), 38 | id: 'feedzy-rss-button-insert', 39 | class: 'insert', 40 | onclick: function( e ) { 41 | insertShortcode( e, editor ); 42 | }, 43 | 44 | }], 45 | }, { 46 | editor: editor, 47 | jquery: $, 48 | wp: wp, 49 | }); 50 | } 51 | }); 52 | }); 53 | 54 | /** 55 | * Gets the translation from the editor (when classic editor is enabled) 56 | * OR 57 | * from the settings array inside the editor (when classic block inside gutenberg) 58 | */ 59 | function getTranslation(editor, slug){ 60 | var string = editor.getLang('feedzy_tinymce_plugin.' + slug); 61 | // if the string is the same as the slug being requested for, look in the settings. 62 | if(string === '{#feedzy_tinymce_plugin.' + slug + '}'){ 63 | string = editor.settings.feedzy_tinymce_plugin[slug]; 64 | } 65 | return string; 66 | } 67 | 68 | function insertShortcode( e, editor ) { 69 | var frame = $( e.currentTarget ).find( 'iframe' ).get( 0 ); 70 | var content = frame.contentDocument; 71 | 72 | var feedzy_form = $( '*[data-feedzy]', content ); 73 | var shortCode = ''; 74 | $.each( feedzy_form, function( index, element ) { 75 | if ( ! $( element ).attr( 'disabled' ) ) { 76 | var shortCodeParams = ''; 77 | var eName = $( element ).attr( 'data-feedzy' ); 78 | var eValue = ''; 79 | if ($( element ).is( 'input' )) { 80 | if ($( element ).attr( 'type' ) === 'radio' || $( element ).attr( 'type' ) === 'checkbox') { 81 | if ( $( element ).is( ':checked' ) ) { 82 | eValue = $( element ).val(); 83 | } 84 | } else { 85 | eValue = $( element ).val(); 86 | } 87 | } else { 88 | eValue = $( element ).val(); 89 | } 90 | 91 | if ( eValue !== '' && typeof eValue !== 'undefined' ) { 92 | shortCodeParams = eName + '="' + eValue + '" '; 93 | } else { 94 | if ( eName === 'feeds' ) { 95 | shortCodeParams = eName + '="https://themeisle.com/feed" '; 96 | } 97 | } 98 | shortCode += shortCodeParams; 99 | } 100 | }); 101 | editor.insertContent( 102 | '[feedzy-rss ' + shortCode + ']' 103 | ); 104 | editor.windowManager.close(); 105 | } 106 | 107 | function openProLink( e , editor ) { 108 | window.open( getTranslation( editor, 'pro_url' ), '_blank' ); 109 | } 110 | })(jQuery); 111 | -------------------------------------------------------------------------------- /js/telemetry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Exclude fields from telemetry tracking. They are not relevant or contain sensitive data. 3 | */ 4 | const excludedFieldsSlugs = [ 5 | '_wp', 6 | 'proxy', 7 | '_key', 8 | '_pass', 9 | 'username', 10 | 'auto', 11 | 'nonce', 12 | 'password', 13 | 'user_ID', 14 | 'post_author', 15 | '_post_status', 16 | 'default_thumbnail_id', 17 | 'default-thumbnail-id' 18 | ]; 19 | 20 | /** 21 | * Collect telemetry data from the submit event of the settings forms. 22 | * 23 | */ 24 | window.addEventListener( 'load', function() { 25 | if ( ! window.tiTrk ) { 26 | return; 27 | } 28 | 29 | /** 30 | * Forms to track. 31 | */ 32 | const envs = [ 33 | { pageName: 'main-settings', formSelector: 'form:has(input[value*="feedzy-settings"])', includeFilter: [] }, 34 | { pageName: 'categories-settings', formSelector: 'form:has(#feedzy_category_feeds)', includeFilter: [ 'feedzy_' ] }, 35 | { pageName: 'import-settings', formSelector: 'form:has(#feedzy-import-form)', includeFilter: [ 'feedzy_meta'] }, 36 | ] 37 | .map( env => { 38 | env.formSelector = document.querySelector( env.formSelector ) 39 | return env; 40 | } ) 41 | .filter( env => { 42 | return env.formSelector; 43 | } ); 44 | 45 | if ( ! envs.length ) { 46 | return; 47 | } 48 | 49 | envs.forEach( env => { 50 | env.formSelector.addEventListener( 'submit', function() { 51 | const formData = new FormData( env.formSelector ); 52 | 53 | for ( const [ name, value ] of formData.entries() ) { 54 | if ( typeof value === 'undefined' || value === null ) { 55 | continue; 56 | } 57 | 58 | if ( excludedFieldsSlugs.some( ( slug ) => name.includes( slug ) ) ) { 59 | continue; 60 | } 61 | 62 | if ( env.includeFilter.length && ! env.includeFilter.some( ( slug ) => name.includes( slug ) ) ) { 63 | continue; 64 | } 65 | 66 | window.tiTrk.with('feedzy').add({ 67 | action: 'snapshot', 68 | feature: env.pageName, 69 | featureComponent: name, 70 | featureValue: value, 71 | }); 72 | } 73 | 74 | window.tiTrk.uploadEvents(); 75 | }); 76 | }); 77 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feedzy-rss-feeds", 3 | "version": "5.0.6", 4 | "description": "Feedzy RSS Feeds - lite version", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/Codeinwp/feedzy-rss-feeds.git" 8 | }, 9 | "keywords": [ 10 | "wordpress-plugin" 11 | ], 12 | "textdomain": "feedzy-rss-feeds", 13 | "category": "plugins", 14 | "author": "ThemeIsle ", 15 | "license": "GPL-2.0+", 16 | "bugs": { 17 | "url": "https://github.com/Codeinwp/feedzy-rss-feeds/issues" 18 | }, 19 | "scripts": { 20 | "build": "npm-run-all build:*", 21 | "build:block": "wp-scripts build --webpack-src-dir=js/FeedzyBlock --output-path=build/block --output-filename=index.js", 22 | "build:loop": "wp-scripts build --webpack-src-dir=js/FeedzyLoop --output-path=build/loop --output-filename=index.js", 23 | "build:onboarding": "wp-scripts build --webpack-src-dir=js/Onboarding --output-path=build/onboarding --output-filename=index.js", 24 | "build:feedback": "wp-scripts build --webpack-src-dir=js/FeedBack --output-path=build/feedback --output-filename=index.js", 25 | "build:actions": "wp-scripts build --webpack-src-dir=js/ActionPopup --output-path=build/action-popup --output-filename=index.js", 26 | "build:conditions": "wp-scripts build --webpack-src-dir=js/Conditions --output-path=build/conditions --output-filename=index.js", 27 | "build:review": "wp-scripts build --webpack-src-dir=js/Review --output-path=build/review --output-filename=index.js", 28 | "dev": "npm-run-all --parallel dev:*", 29 | "dev:block": "wp-scripts start --webpack-src-dir=js/FeedzyBlock --output-path=build/block --output-filename=index.js", 30 | "dev:loop": "wp-scripts start --webpack-src-dir=js/FeedzyLoop --output-path=build/loop --output-filename=index.js", 31 | "dev:onboarding": "wp-scripts start --webpack-src-dir=js/Onboarding --output-path=build/onboarding --output-filename=index.js", 32 | "dev:feedback": "wp-scripts start --webpack-src-dir=js/FeedBack --output-path=build/feedback --output-filename=index.js", 33 | "dev:actions": "wp-scripts start --webpack-src-dir=js/ActionPopup --output-path=build/action-popup --output-filename=index.js", 34 | "dev:conditions": "wp-scripts start --webpack-src-dir=js/Conditions --output-path=build/conditions --output-filename=index.js", 35 | "lint:js": "wp-scripts lint-js ./js", 36 | "release": "semantic-release --debug", 37 | "dist": "bash bin/dist.sh", 38 | "grunt": "grunt", 39 | "test:e2e": "wp-scripts test-playwright --config tests/e2e/playwright.config.js", 40 | "test:e2e:debug": "wp-scripts test-playwright --config tests/e2e/playwright.config.js --ui", 41 | "wp-env": "wp-env" 42 | }, 43 | "pot": { 44 | "reportmsgidbugsto": "https://github.com/Codeinwp/feedzy-rss-feeds/issues", 45 | "languageteam": "Themeisle Translate ", 46 | "lasttranslator": "Themeisle Translate Team " 47 | }, 48 | "devDependencies": { 49 | "@playwright/test": "^1.44.0", 50 | "@semantic-release/changelog": "^5.0.1", 51 | "@semantic-release/exec": "^5.0.0", 52 | "@semantic-release/git": "^9.0.0", 53 | "@wordpress/components": "^27.6.0", 54 | "@wordpress/compose": "^6.35.0", 55 | "@wordpress/data": "^9.28.0", 56 | "@wordpress/dom-ready": "^3.58.0", 57 | "@wordpress/e2e-test-utils-playwright": "^0.26.0", 58 | "@wordpress/element": "^5.35.0", 59 | "@wordpress/env": "^9.10.0", 60 | "@wordpress/eslint-plugin": "^22.0.0", 61 | "@wordpress/i18n": "^4.58.0", 62 | "@wordpress/icons": "^9.49.0", 63 | "@wordpress/media-utils": "^4.49.0", 64 | "@wordpress/scripts": "^27.9.0", 65 | "classnames": "^2.3.2", 66 | "conventional-changelog-simple-preset": "^1.0.24", 67 | "dayjs": "^1.10.4", 68 | "eslint": "^8.57.1", 69 | "grunt": "^1.4.0", 70 | "grunt-version": "^2.0.0", 71 | "grunt-wp-readme-to-markdown": "^2.0.1", 72 | "lodash": "^4.17.21", 73 | "npm-run-all": "^4.1.5", 74 | "query-string": "^7.0.0", 75 | "raw-loader": "^4.0.2", 76 | "replace-in-file": "^6.2.0", 77 | "semantic-release": "^17.4.2", 78 | "semantic-release-slack-bot": "^2.1.0" 79 | }, 80 | "dependencies": { 81 | "array-move": "^4.0.0", 82 | "react-joyride": "^2.8.2", 83 | "react-sortable-hoc": "^2.0.0" 84 | }, 85 | "overrides": { 86 | "react-sortable-hoc": { 87 | "react": "18.3.1", 88 | "react-dom": "18.3.1" 89 | }, 90 | "@wordpress/components": { 91 | "react": "18.3.1", 92 | "react-dom": "18.3.1" 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Themeisle rules for PHP_CodeSnifferr 4 | 5 | . 6 | 7 | node_modules/* 8 | vendor/* 9 | lib/* 10 | tests/* 11 | 12 | 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 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | set_role( 'administrator' ); 39 | wp_update_user( 40 | array( 41 | 'ID' => 1, 42 | 'first_name' => 'Admin', 43 | 'last_name' => 'User', 44 | ) 45 | ); 46 | -------------------------------------------------------------------------------- /tests/e2e/config/flaky-tests-reporter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A **flaky** test is defined as a test which passed after auto-retrying. 3 | * - By default, all tests run once if they pass. 4 | * - If a test fails, it will automatically re-run at most 2 times. 5 | * - If it pass after retrying (below 2 times), then it's marked as **flaky** 6 | * but displayed as **passed** in the original test suite. 7 | * - If it fail all 3 times, then it's a **failed** test. 8 | */ 9 | /** 10 | * External dependencies 11 | */ 12 | import fs from 'fs'; 13 | import filenamify from 'filenamify'; 14 | 15 | 16 | // Remove "steps" to prevent stringify circular structure. 17 | function formatTestResult( testResult ) { 18 | const result = { ...testResult, steps: undefined }; 19 | delete result.steps; 20 | return result; 21 | } 22 | 23 | class FlakyTestsReporter { 24 | failingTestCaseResults = new Map(); 25 | 26 | onBegin() { 27 | try { 28 | fs.mkdirSync( 'flaky-tests' ); 29 | } catch ( err ) { 30 | if ( 31 | err instanceof Error && 32 | err.code === 'EEXIST' 33 | ) { 34 | // Ignore the error if the directory already exists. 35 | } else { 36 | throw err; 37 | } 38 | } 39 | } 40 | 41 | onTestEnd( test, testCaseResult ) { 42 | const testPath = test.location.file; 43 | const testTitle = test.title; 44 | 45 | switch ( test.outcome() ) { 46 | case 'unexpected': { 47 | if ( ! this.failingTestCaseResults.has( testTitle ) ) { 48 | this.failingTestCaseResults.set( testTitle, [] ); 49 | } 50 | this.failingTestCaseResults 51 | .get( testTitle ) 52 | .push( formatTestResult( testCaseResult ) ); 53 | break; 54 | } 55 | case 'flaky': { 56 | fs.writeFileSync( 57 | `flaky-tests/${ filenamify( testTitle ) }.json`, 58 | JSON.stringify( { 59 | version: 1, 60 | runner: '@playwright/test', 61 | title: testTitle, 62 | path: testPath, 63 | results: this.failingTestCaseResults.get( testTitle ), 64 | } ), 65 | 'utf-8' 66 | ); 67 | break; 68 | } 69 | default: 70 | break; 71 | } 72 | } 73 | 74 | onEnd() { 75 | this.failingTestCaseResults.clear(); 76 | } 77 | 78 | printsToStdio() { 79 | return false; 80 | } 81 | } 82 | 83 | module.exports = FlakyTestsReporter; 84 | -------------------------------------------------------------------------------- /tests/e2e/config/global-setup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import { request } from '@playwright/test'; 5 | 6 | /** 7 | * WordPress dependencies 8 | */ 9 | import { RequestUtils } from '@wordpress/e2e-test-utils-playwright'; 10 | 11 | async function globalSetup( config ) { 12 | const { storageState, baseURL } = config.projects[ 0 ].use; 13 | const storageStatePath = 14 | typeof storageState === 'string' ? storageState : undefined; 15 | 16 | const requestContext = await request.newContext( { 17 | baseURL, 18 | } ); 19 | 20 | const requestUtils = new RequestUtils( requestContext, { 21 | storageStatePath, 22 | } ); 23 | 24 | // Authenticate and save the storageState to disk. 25 | await requestUtils.setupRest(); 26 | 27 | // Reset the test environment before running the tests. 28 | await Promise.all( [ 29 | requestUtils.activateTheme( 'twentytwentyone' ), 30 | // Disable this test plugin as it's conflicting with some of the tests. 31 | // We already have reduced motion enabled and Playwright will wait for most of the animations anyway. 32 | // requestUtils.deactivatePlugin( 33 | // 'gutenberg-test-plugin-disables-the-css-animations' 34 | // ), 35 | requestUtils.deleteAllPosts(), 36 | requestUtils.deleteAllBlocks(), 37 | requestUtils.resetPreferences(), 38 | ] ); 39 | 40 | await requestContext.dispose(); 41 | } 42 | 43 | export default globalSetup; 44 | -------------------------------------------------------------------------------- /tests/e2e/playwright.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * External dependencies 3 | */ 4 | import os from 'os'; 5 | import { fileURLToPath } from 'url'; 6 | import { defineConfig, devices } from '@playwright/test'; 7 | 8 | /** 9 | * WordPress dependencies 10 | */ 11 | const baseConfig = require( '@wordpress/scripts/config/playwright.config' ); 12 | 13 | const config = defineConfig( { 14 | ...baseConfig, 15 | reporter: process.env.CI 16 | ? [ [ 'github' ], [ './config/flaky-tests-reporter.js' ] ] 17 | : 'list', 18 | workers: 1, 19 | globalSetup: fileURLToPath( 20 | new URL( './config/global-setup.js', 'file:' + __filename ).href 21 | ), 22 | projects: [ 23 | { 24 | name: 'chromium', 25 | use: { ...devices[ 'Desktop Chrome' ] }, 26 | grepInvert: /-chromium/, 27 | }, 28 | // { 29 | // name: 'webkit', 30 | // use: { 31 | // ...devices[ 'Desktop Safari' ], 32 | // /** 33 | // * Headless webkit won't receive dataTransfer with custom types in the 34 | // * drop event on Linux. The solution is to use `xvfb-run` to run the tests. 35 | // * ```sh 36 | // * xvfb-run npm run test:e2e 37 | // * ``` 38 | // * See `.github/workflows/end2end-test-playwright.yml` for advanced usages. 39 | // */ 40 | // headless: os.type() !== 'Linux', 41 | // }, 42 | // grep: /@webkit/, 43 | // grepInvert: /-webkit/, 44 | // }, 45 | // { 46 | // name: 'firefox', 47 | // use: { ...devices[ 'Desktop Firefox' ] }, 48 | // grep: /@firefox/, 49 | // grepInvert: /-firefox/, 50 | // }, 51 | ], 52 | } ); 53 | 54 | export default config; 55 | -------------------------------------------------------------------------------- /tests/e2e/specs/feed.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | import { test, expect } from '@wordpress/e2e-test-utils-playwright'; 5 | import { 6 | tryCloseTourModal, 7 | deleteAllFeedImports, 8 | addFeeds, 9 | runFeedImport 10 | } from '../utils'; 11 | 12 | test.describe( 'Feed Settings', () => { 13 | 14 | const FEED_URL = 'https://s3.amazonaws.com/verti-utils/sample-feed-import.xml'; 15 | 16 | test.beforeEach( async ( { requestUtils } ) => { 17 | await deleteAllFeedImports( requestUtils ); 18 | await requestUtils.deleteAllPosts(); 19 | await requestUtils.deleteAllMedia(); 20 | } ); 21 | 22 | test( 'adding an URL feed', async({ editor, page }) => { 23 | 24 | const importName = 'Test Title: adding an URL feed'; 25 | 26 | await page.goto('/wp-admin/post-new.php?post_type=feedzy_imports'); 27 | await tryCloseTourModal( page ); 28 | 29 | await page.getByPlaceholder('Add a name for your import').fill(importName); 30 | 31 | // Add feed URL via tag input. 32 | await page.getByPlaceholder('Paste your feed URL and click').fill(FEED_URL); 33 | await page.getByPlaceholder('Paste your feed URL and click').press('Enter'); 34 | await expect( page.getByText( FEED_URL ) ).toBeVisible(); 35 | 36 | await addFeeds( page, [FEED_URL] ); 37 | 38 | await page.getByRole('button', { name: 'Save', exact: true }).click({ force: true, clickCount: 1 }); 39 | 40 | await expect( page.getByRole('cell', { name: importName }) ).toBeVisible(); 41 | 42 | await page.getByRole('cell', { name: importName }).hover(); // Display the actions. 43 | await page.getByLabel(`Edit “${importName}`).click(); 44 | 45 | expect( await page.getByPlaceholder('Add a name for your import').inputValue() ).toBe(importName); 46 | await expect( page.getByText( FEED_URL ) ).toBeVisible(); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /tests/e2e/specs/loop.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | import { test, expect } from '@wordpress/e2e-test-utils-playwright'; 5 | 6 | test.describe('Feedzy Loop', () => { 7 | const FEED_URL = 8 | 'https://s3.amazonaws.com/verti-utils/sample-feed-import.xml'; 9 | const POST_TITLE = `Feedzy Loop Test ${Math.floor(Math.random() * 1000)}`; 10 | 11 | test('add Feedzy Loop Block', async ({ editor, page }) => { 12 | await page.goto('/wp-admin/post-new.php?post_type=feedzy_categories'); 13 | await page.getByLabel('Add title').click(); 14 | await page.keyboard.type('Group One'); 15 | 16 | await page.locator('textarea[name="feedzy_category_feed"]').click(); 17 | await page.keyboard.type(FEED_URL); 18 | await page 19 | .getByRole('button', { name: 'Publish', exact: true }) 20 | .click(); 21 | await page.waitForTimeout(1000); 22 | 23 | await page.goto('/wp-admin/post-new.php'); 24 | 25 | if ( 26 | (await page.$( 27 | '.edit-post-welcome-guide .components-modal__header button' 28 | )) !== null 29 | ) { 30 | await page.click( 31 | '.edit-post-welcome-guide .components-modal__header button' 32 | ); 33 | } 34 | 35 | await page.getByLabel('Add title').click(); 36 | await page.keyboard.type(POST_TITLE); 37 | 38 | await page.getByLabel('Add block').click(); 39 | 40 | await page.getByPlaceholder('Search').click(); 41 | await page.keyboard.type('Feedzy Loop'); 42 | await page.waitForTimeout(1000); 43 | 44 | await page.getByRole('option', { name: ' Feedzy Loop' }).click(); 45 | await page.waitForTimeout(1000); 46 | 47 | await page.getByPlaceholder('Enter URLs or select a').click(); 48 | await page.keyboard.type(FEED_URL); 49 | 50 | const loadFeedButton = await page.getByRole('button', { 51 | name: 'Load Feed', 52 | exact: true, 53 | }); 54 | const isDisabled = await loadFeedButton.isDisabled(); 55 | expect(isDisabled).toBe(false); 56 | await loadFeedButton.click(); 57 | await page.waitForTimeout(1000); 58 | 59 | await page.getByLabel('Display curated RSS content').click(); 60 | await page.waitForTimeout(1000); 61 | 62 | // Now that we have tested we can insert URL, we can test the Feed Group. 63 | 64 | await page 65 | .getByLabel('Block: Feedzy Loop') 66 | .locator('div') 67 | .nth(1) 68 | .click(); 69 | await page.getByRole('button', { name: 'Edit Feed' }).click(); 70 | 71 | await page.getByRole('button', { name: 'Select Feed Group' }).click(); 72 | await page.locator('.fz-dropdown-item').first().click(); 73 | 74 | await page 75 | .getByRole('button', { name: 'Load Feed', exact: true }) 76 | .click(); 77 | await page.waitForTimeout(1000); 78 | 79 | await page 80 | .getByRole('button', { name: 'Publish', exact: true }) 81 | .click(); 82 | await page.waitForTimeout(1000); 83 | 84 | await page 85 | .getByLabel('Editor publish') 86 | .getByRole('button', { name: 'Publish', exact: true }) 87 | .click(); 88 | await page.waitForTimeout(5000); 89 | 90 | const snackbar = await page.getByTestId('snackbar'); 91 | const snackbarText = await snackbar.textContent(); 92 | expect(snackbarText).toContain('Post published.'); 93 | 94 | await page.goto('/wp-admin/edit.php'); 95 | 96 | const postTitle = await page.locator('a.row-title').first(); 97 | await postTitle.hover(); 98 | await page.getByLabel('View “' + POST_TITLE + '”').click(); 99 | 100 | await page.waitForTimeout(5000); 101 | 102 | // We want to confirm .wp-block-feedzy-rss-feeds-loop is present and it has 5 children 103 | const feedzyLoop = await page.$('.wp-block-feedzy-rss-feeds-loop'); 104 | expect(feedzyLoop).not.toBeNull(); 105 | 106 | const feedzyLoopChildren = await feedzyLoop.$$(':scope > *'); 107 | expect(feedzyLoopChildren.length).toBe(5); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /tests/e2e/specs/upsell.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * WordPress dependencies 3 | */ 4 | import { test, expect } from '@wordpress/e2e-test-utils-playwright'; 5 | import { tryCloseTourModal, deleteAllFeedImports } from '../utils'; 6 | 7 | test.describe( 'Upsell', () => { 8 | test.beforeEach( async ( { requestUtils, page } ) => { 9 | await deleteAllFeedImports( requestUtils ); 10 | await requestUtils.deleteAllPosts(); 11 | 12 | await page.goto('/wp-admin/post-new.php?post_type=feedzy_imports'); 13 | await tryCloseTourModal( page ); 14 | } ); 15 | 16 | 17 | test( 'filters', async({ editor, page }) => { 18 | await page.getByRole('button', { name: 'Step 2 Filters' }).click({ force: true }); 19 | 20 | // Hover over text named Filter by Keyword 21 | const filtersTab = page.locator('#feedzy-import-form > div.feedzy-accordion > div:nth-child(2)'); 22 | 23 | // It should have 1 elements with .only-pro-content class. 24 | await expect( filtersTab.locator('.pro-label').count() ).resolves.toBe(1); 25 | 26 | } ); 27 | 28 | 29 | test( 'general settings', async({ editor, page }) => { 30 | await page.getByRole('button', { name: 'Step 4 General feed settings' }).click({ force: true }); 31 | 32 | 33 | 34 | await page.locator('.fz-form-group:has( #feed-post-default-thumbnail )').hover({ force: true }); 35 | let upgradeAlert = page.locator('#feedzy-import-form a[href*="utm_campaign=fallback-image"]'); 36 | await expect( upgradeAlert ).toBeVisible(); 37 | 38 | await page.locator('.fz-form-group:has( #fz-event-schedule )').scrollIntoViewIfNeeded() 39 | await page.locator('.fz-form-group:has( #fz-event-schedule )').hover({ force: true }); 40 | upgradeAlert = page.locator('#feedzy-import-form a[href*="utm_campaign=schedule-import-job"]'); 41 | await expect( upgradeAlert ).toBeVisible(); 42 | 43 | await page.locator('.fz-form-group:has( #delete-attached-media )').hover({ force: true }); 44 | upgradeAlert = page.locator('#feedzy-import-form a[href*="utm_campaign=delete-featured-image"]'); 45 | await expect( upgradeAlert ).toBeVisible(); 46 | 47 | await page.locator('.fz-form-group:has( #feedzy_mark_duplicate )').hover({ force: true }); 48 | upgradeAlert = page.locator('#feedzy-import-form a[href*="utm_campaign=remove-duplicates"]'); 49 | await expect( upgradeAlert ).toBeVisible(); 50 | } ); 51 | }); 52 | 53 | test.describe( 'List Page Upsell', () => { 54 | test.beforeEach( async ( { requestUtils, page } ) => { 55 | await page.goto('/wp-admin/edit.php?post_type=feedzy_imports'); 56 | } ); 57 | 58 | test('Import/Export', async ({ editor, page }) => { 59 | // Locate and click the "Import Job" link 60 | const importButton = page.locator('.fz-export-import-btn'); 61 | await expect(importButton).toBeVisible(); 62 | await importButton.click(); 63 | 64 | // Wait for the popup to become visible 65 | const upsellPopup = page.locator('#fz_import_export_upsell'); 66 | await expect(upsellPopup).toBeVisible(); 67 | 68 | // Locate and check the "Upgrade to PRO" link inside the popup 69 | const upgradeToProLink = upsellPopup.locator('a', { hasText: 'Upgrade to PRO' }); 70 | await expect(upgradeToProLink).toBeVisible(); 71 | 72 | // Get the URL from the "Upgrade to PRO" link 73 | const upsellLink = new URL(await upgradeToProLink.getAttribute('href')); 74 | 75 | // Validate the URL parameters 76 | expect(upsellLink.host).toBe('themeisle.com'); 77 | expect(upsellLink.searchParams.get('utm_source')).toBe('wpadmin'); 78 | expect(upsellLink.searchParams.get('utm_medium')).toBe('edit'); 79 | expect(upsellLink.searchParams.get('utm_content')).toBe('feedzy-rss-feeds'); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /tests/test-image-import.php: -------------------------------------------------------------------------------- 1 | getMethod( 'try_save_featured_image' ); 23 | $try_save_featured_image->setAccessible( true ); 24 | 25 | // Check that NON-IMAGE URL returns invalid 26 | $import_errors = array(); 27 | $import_info = array(); 28 | $arguments = array( 'a random string', 0, 'Post Title', &$import_errors, &$import_info, array() ); 29 | $response = $try_save_featured_image->invokeArgs( $feedzy, $arguments ); 30 | 31 | $this->assertFalse( $response ); 32 | 33 | $this->assertTrue( count( $import_errors ) > 0 ); 34 | $this->assertEquals( 'Invalid Featured Image URL: a random string', $import_errors[0] ); 35 | 36 | 37 | // For the next test, we will use a valid URL, but the image does not exist. We will check that the error is logged and is the expected one. 38 | add_filter( 'themeisle_log_event', function( $product, $message, $type, $file, $line ) { 39 | if ( $type === 'error' ) { 40 | $this->assertTrue( strpos( $message, 'Unable to download file' ) !== false ); 41 | } 42 | }, 10, 5 ); 43 | 44 | $import_errors = array(); 45 | $import_info = array(); 46 | $arguments = array( 'https://example.com/path_to_image/image.jpeg', 0, 'Post Title', &$import_errors, &$import_info, array() ); 47 | $response = $try_save_featured_image->invokeArgs( $feedzy, $arguments ); 48 | 49 | // expected response is false because the image does not exist, but the URL is valid so no $import_errors should be set. 50 | $this->assertFalse( $response ); 51 | $this->assertTrue( empty( $import_errors ) ); 52 | 53 | $import_errors = array(); 54 | $import_info = array(); 55 | $arguments = array( 'https://example.com/path_to_image/image w space in name.jpeg', 0, 'Post Title', &$import_errors, &$import_info, array() ); 56 | $response = $try_save_featured_image->invokeArgs( $feedzy, $arguments ); 57 | 58 | // expected response is false because the image does not exist, but the URL is valid so no $import_errors should be set. 59 | $this->assertFalse( $response ); 60 | $this->assertTrue( empty( $import_errors ) ); 61 | } 62 | 63 | public function test_import_image_special_characters() { 64 | $feedzy = new Feedzy_Rss_Feeds_Import( 'feedzy-rss-feeds', '1.2.0' ); 65 | 66 | $reflector = new ReflectionClass( $feedzy ); 67 | $try_save_featured_image = $reflector->getMethod( 'try_save_featured_image' ); 68 | $try_save_featured_image->setAccessible( true ); 69 | 70 | $import_errors = array(); 71 | $import_info = array(); 72 | 73 | $arguments = array( 'https://example.com/path_to_image/çöp.jpg?itok=ZYU_ihPB', 0, 'Post Title', &$import_errors, &$import_info, array() ); 74 | $response = $try_save_featured_image->invokeArgs( $feedzy, $arguments ); 75 | 76 | // expected response is false because the image does not exist, but the URL is valid so no $import_errors should be set. 77 | $this->assertFalse( $response ); 78 | $this->assertTrue( empty( $import_errors ) ); 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/test-post-access.php: -------------------------------------------------------------------------------- 1 | get_rand_name(); 32 | $admin_id = $this->factory->user->create( 33 | array( 34 | 'role' => 'administrator', 35 | ) 36 | ); 37 | wp_set_current_user( $admin_id ); 38 | $p = $this->factory->post->create_and_get( 39 | array( 40 | 'post_title' => $random_name, 41 | 'post_type' => 'feedzy_categories', 42 | 'post_author' => $admin_id, 43 | ) 44 | ); 45 | do_action( 'save_post', $p->ID, $p ); 46 | $this->assertEquals( $p->post_title, $random_name ); 47 | $this->assertEquals( $p->post_type, 'feedzy_categories' ); 48 | 49 | $this->assertTrue( feedzy_current_user_can() ); 50 | $this->assertTrue( current_user_can( 'edit_post', $p->ID ) ); 51 | 52 | 53 | $contributor_id = $this->factory->user->create( 54 | array( 55 | 'role' => 'contributor', 56 | ) 57 | ); 58 | wp_set_current_user( $contributor_id ); 59 | 60 | $this->assertFalse( feedzy_current_user_can() ); 61 | $this->assertFalse( current_user_can( 'edit_post', $p->ID ) ); 62 | 63 | } 64 | 65 | public function test_contributor_user_with_errors() { 66 | $feedzy = new Feedzy_Rss_Feeds_Admin('feedzy', 'latest'); 67 | $contributor_id = $this->factory->user->create( 68 | array( 69 | 'role' => 'contributor', 70 | ) 71 | ); 72 | wp_set_current_user( $contributor_id ); 73 | $post_id = $this->factory->post->create( array( 'post_author' => get_current_user_id() ) ); 74 | $GLOBALS['post'] = get_post( $post_id ); 75 | // Mock feed object and errors. 76 | $feed = (object) array( 'multifeed_url' => array( 'http://example.com/feed' ) ); 77 | $errors = array( 'Error 1' ); 78 | 79 | 80 | $actual_output = $feedzy->feedzy_default_error_notice( $errors, $feed, 'http://example.com/feed' ); 81 | 82 | $this->assertEquals( '', $actual_output ); 83 | } 84 | public function test_author_user_with_errors_admin_screen() { 85 | $feedzy = new Feedzy_Rss_Feeds_Admin('feedzy', 'latest'); 86 | 87 | $contributor_id = $this->factory->user->create( 88 | array( 89 | 'role' => 'author', 90 | ) 91 | ); 92 | wp_set_current_user( $contributor_id ); 93 | 94 | $post_id = $this->factory->post->create( array( 'post_author' => get_current_user_id() ) ); 95 | $GLOBALS['post'] = get_post( $post_id ); 96 | // Mock feed object and errors. 97 | $feed = (object) array( 'multifeed_url' => array( 'http://example.com/feed' ) ); 98 | $errors = array( 'Error 1' ); 99 | 100 | set_current_screen('admin.php'); 101 | $actual_output = $feedzy->feedzy_default_error_notice( $errors, $feed, 'http://example.com/feed' ); 102 | 103 | $this->assertEquals( '', $actual_output ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /uninstall.php: -------------------------------------------------------------------------------- 1 |