├── .babelrc ├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question_template.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── deploy.yml │ └── test.yml ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── assets ├── banner-772x250.png ├── icon-256x256.png ├── icon.svg ├── screenshot-1.png ├── screenshot-10.png ├── screenshot-11.png ├── screenshot-2.png ├── screenshot-3.png ├── screenshot-4.png ├── screenshot-5.png ├── screenshot-6.png ├── screenshot-7.png ├── screenshot-8.png └── screenshot-9.png ├── bin ├── deploy.sh ├── install-wp-tests.sh ├── publish.sh └── test.sh ├── composer.json ├── composer.lock ├── disqus ├── LICENSE.txt ├── README.txt ├── admin │ ├── class-disqus-admin.php │ ├── css │ │ └── disqus-admin.css │ ├── index.php │ └── partials │ │ └── disqus-admin-partial.php ├── disqus.php ├── includes │ ├── class-disqus-api-service.php │ ├── class-disqus-deactivator.php │ ├── class-disqus-loader.php │ ├── class-disqus.php │ └── index.php ├── index.php ├── public │ ├── class-disqus-public.php │ ├── index.php │ ├── js │ │ ├── comment_count.js │ │ └── comment_embed.js │ └── partials │ │ ├── disqus-public-display.php │ │ └── disqus-public-sso-login-profile.php ├── rest-api │ ├── class-disqus-rest-api.php │ └── index.php └── uninstall.php ├── docker-compose.test.yml ├── docker-compose.yml ├── docs ├── CHANGELOG.md └── CONTRIBUTING.md ├── frontend ├── src │ └── ts │ │ ├── DisqusApi.ts │ │ ├── REST_OPTIONS.ts │ │ ├── WordPressCommentExporter.ts │ │ ├── WordPressRestApi.ts │ │ ├── actions.ts │ │ ├── app.tsx │ │ ├── components │ │ ├── Admin.tsx │ │ ├── AdvancedConfigForm.tsx │ │ ├── ExportComments.tsx │ │ ├── FormProps.ts │ │ ├── HelpResources.tsx │ │ ├── Install.tsx │ │ ├── Loading.tsx │ │ ├── Main.tsx │ │ ├── ManualSyncForm.tsx │ │ ├── Message.tsx │ │ ├── SSOConfigForm.tsx │ │ ├── SiteConfigForm.tsx │ │ ├── SupportDiagnostics.tsx │ │ ├── SupportLinks.tsx │ │ ├── SyncConfigForm.tsx │ │ ├── SyncTokenForm.tsx │ │ ├── WelcomePanel.tsx │ │ └── WhatsNew.tsx │ │ ├── constants │ │ └── links.tsx │ │ ├── containers │ │ ├── AdvancedConfigContainer.ts │ │ ├── ExportCommentsContainer.ts │ │ ├── InstallContainer.ts │ │ ├── MainContainer.ts │ │ ├── ManualSyncContainer.ts │ │ ├── SSOConfigContainer.ts │ │ ├── SiteConfigContainer.ts │ │ ├── SupportDiagnosticsContainer.ts │ │ ├── SyncConfigContainer.ts │ │ ├── SyncTokenContainer.ts │ │ ├── mapDispatchToProps.ts │ │ └── mapStateToProps.ts │ │ ├── index.ts │ │ ├── reducers │ │ ├── AdminOptions.ts │ │ ├── AdminState.ts │ │ ├── SyncStatus.ts │ │ └── adminApp.ts │ │ ├── typings │ │ └── i18n.d.ts │ │ └── utils.ts └── tests │ ├── DisqusApi.test.ts │ ├── WordPressCommentExporter.test.ts │ ├── WordPressRestApi.test.ts │ ├── actions.test.ts │ ├── beforeAll.ts │ ├── components │ ├── AdvancedConfigForm.test.tsx │ ├── ExportComments.test.tsx │ ├── HelpResources.test.tsx │ ├── Install.test.tsx │ ├── Message.test.tsx │ ├── SSOConfigForm.test.tsx │ ├── SiteConfigForm.test.tsx │ ├── SupportDiagnostics.test.tsx │ ├── SupportLinks.test.tsx │ ├── SyncConfigForm.test.tsx │ ├── SyncTokenForm.test.tsx │ ├── WelcomePanel.test.tsx │ └── __snapshots__ │ │ ├── AdvancedConfigForm.test.tsx.snap │ │ ├── ExportComments.test.tsx.snap │ │ ├── HelpResources.test.tsx.snap │ │ ├── Install.test.tsx.snap │ │ ├── Message.test.tsx.snap │ │ ├── SSOConfigForm.test.tsx.snap │ │ ├── SiteConfigForm.test.tsx.snap │ │ ├── SupportLinks.test.tsx.snap │ │ ├── SyncConfigForm.test.tsx.snap │ │ ├── SyncTokenForm.test.tsx.snap │ │ └── WelcomePanel.test.tsx.snap │ ├── containers │ ├── mapDispatchToProps.test.ts │ └── mapStateToProps.test.ts │ ├── fixtures.ts │ ├── reducers │ ├── AdminOptions.test.ts │ ├── AdminState.test.ts │ ├── SyncStatus.test.ts │ └── adminApp.test.ts │ ├── typings │ └── NodeJS.d.ts │ └── utils.test.ts ├── package.json ├── phpcs.ruleset.xml ├── phpunit.xml.dist ├── tests ├── bootstrap.php ├── test-admin.php ├── test-api-service.php ├── test-plugin.php ├── test-rest-api-basic.php ├── test-rest-api-export.php ├── test-rest-api-settings.php └── test-rest-api-sync.php ├── tsconfig.json ├── tslint.json ├── webpack.config.js ├── wp-su.sh └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "react" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | vendor 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us address an issue 4 | --- 5 | 6 | ### Describe the bug 7 | A clear and concise description of what the bug is. 8 | 9 | ### To Reproduce 10 | Steps to reproduce the behavior: 11 | 1. Go to '...' 12 | 2. Click on '....' 13 | 3. Scroll down to '....' 14 | 4. See error 15 | 16 | ### Expected behavior 17 | A clear and concise description of what you expected to happen. 18 | 19 | ### Screenshots 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | ### Specifications: 23 | - Wordpress version: 24 | - Plugin version: 25 | 26 | ### Additional context 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an new feature for this plugin 4 | --- 5 | 6 | ### Is your feature request related to a problem? Please describe. 7 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 8 | 9 | ### Describe the solution you'd like 10 | A clear and concise description of what you want to happen. 11 | 12 | ### Describe alternatives you've considered 13 | A clear and concise description of any alternative solutions or features you've considered. 14 | 15 | ### Additional context 16 | Add any other context or screenshots about the feature request here. 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about the package 4 | --- 5 | 6 | Please make sure to check for [duplicate questions](https://github.com/disqus/disqus-wordpress-plugin/issues?q=label%3Aquestion) and the [Disqus Knowledge base](https://help.disqus.com/) before submitting a new question. 7 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | 10 | ## How Has This Been Tested? 11 | 12 | 13 | 14 | 15 | ## Screenshots (if appropriate): 16 | 17 | ## Types of changes 18 | 19 | - [ ] Bug fix (non-breaking change which fixes an issue) 20 | - [ ] New feature (non-breaking change which adds functionality) 21 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 22 | 23 | ## Checklist: 24 | 25 | 26 | - [ ] My code follows the code style of this project. 27 | - [ ] My change requires a change to the documentation. 28 | - [ ] I have updated the documentation accordingly. 29 | - [ ] All new and existing tests passed. 30 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy plugin 2 | run-name: Deploying ${{ github.ref }} (${{ github.sha }}) 3 | on: workflow_dispatch 4 | 5 | env: 6 | SVNUSER: ${{ vars.SVNUSER }} 7 | SVNURL: ${{ vars.SVNURL }} 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-22.04 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 16.x 21 | 22 | - name: Install Node.js dependencies 23 | run: yarn install 24 | 25 | - name: Build frontend 26 | run: yarn run build 27 | 28 | - name: Run deploy script 29 | run: bash bin/deploy.sh 30 | env: 31 | SVNPASS: ${{ secrets.SVNPASS }} 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | run-name: Running tests for ${{ github.ref }} 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-22.04 8 | 9 | strategy: 10 | matrix: 11 | php: ["7.4", "8.0", "8.1", "8.2"] 12 | mysql: ["5.7", "8.0"] 13 | wordpress: ["6.5", "6.4", "6.3", "6.2", "6.0", "5.9"] 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v3 18 | 19 | - name: Setup PHP 20 | uses: shivammathur/setup-php@v2 21 | with: 22 | php-version: ${{ matrix.php }} 23 | tools: composer, phpunit:8 24 | 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v3 27 | 28 | - name: Setup MySQL 29 | uses: shogo82148/actions-setup-mysql@v1 30 | with: 31 | mysql-version: ${{ matrix.mysql }} 32 | 33 | - name: Install PHP dependencies 34 | run: composer install 35 | 36 | - name: Install Node.js dependencies 37 | run: yarn install 38 | 39 | - name: Install WordPress 40 | run: bin/install-wp-tests.sh wordpress_test root "" "127.0.0.1" ${{ matrix.wordpress }} 41 | 42 | - name: Run PHPCS 43 | run: ./vendor/bin/phpcs -s --standard=phpcs.ruleset.xml 44 | 45 | - name: Run frontend tests 46 | run: yarn test 47 | 48 | - name: Run backend tests 49 | run: bin/test.sh 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Javascript 2 | node_modules 3 | 4 | # PHPUnit 5 | .phpunit.result.cache 6 | .phpunit.cache 7 | 8 | # Numerous always-ignore extensions 9 | 10 | *.diff 11 | *.err 12 | *.orig 13 | *.log 14 | *.rej 15 | *.swo 16 | *.swp 17 | *.vi 18 | *~ 19 | *.sass-cache 20 | 21 | # OS or Editor folders 22 | 23 | .DS_Store 24 | Thumbs.db 25 | .cache 26 | .project 27 | .settings 28 | .tmproj 29 | *.esproj 30 | nbproject 31 | *.sublime-project 32 | *.sublime-workspace 33 | .vscode 34 | .nvmrc 35 | tags 36 | 37 | # Build files 38 | 39 | disqus.zip 40 | vendor/ 41 | disqus/admin/bundles/ 42 | tmp/ 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM wordpress:latest 2 | 3 | # Install system dependencies 4 | RUN set -ex; \ 5 | apt-get update; \ 6 | apt-get install -y \ 7 | default-mysql-client \ 8 | less \ 9 | nodejs \ 10 | subversion \ 11 | sudo \ 12 | yarnpkg \ 13 | ; \ 14 | apt-get clean; \ 15 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 16 | 17 | # Add WP-CLI 18 | RUN curl -o /bin/wp-cli.phar https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar 19 | COPY wp-su.sh /bin/wp 20 | RUN chmod +x /bin/wp-cli.phar /bin/wp 21 | 22 | # Add Composer 23 | COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer 24 | 25 | # Install dockerize 26 | ENV DOCKERIZE_VERSION 0.6.1 27 | RUN set -ex; \ 28 | curl -L -O "https://github.com/jwilder/dockerize/releases/download/v${DOCKERIZE_VERSION}/dockerize-linux-amd64-v${DOCKERIZE_VERSION}.tar.gz"; \ 29 | tar xzvf dockerize-linux-amd64-v${DOCKERIZE_VERSION}.tar.gz; \ 30 | mv dockerize /usr/local/bin/dockerize; \ 31 | dockerize -version; \ 32 | rm dockerize-linux-amd64-v${DOCKERIZE_VERSION}.tar.gz 33 | 34 | # Install test dependencies 35 | RUN mkdir -p /usr/src/app 36 | COPY composer.json composer.lock package.json yarn.lock /usr/src/app/ 37 | COPY bin/install-wp-tests.sh /usr/src/app/bin/ 38 | RUN set -ex; \ 39 | cd /usr/src/app; \ 40 | bin/install-wp-tests.sh "" "" "" "" latest true; \ 41 | composer install; \ 42 | yarnpkg 43 | COPY . /usr/src/app 44 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CURPATH := $(abspath $(lastword $(MAKEFILE_LIST))) 2 | CURDIR := $(notdir $(patsubst %/,%,$(dir $(CURPATH)))) 3 | 4 | lint: 5 | ./vendor/bin/phpcs --standard=phpcs.ruleset.xml 6 | 7 | run: 8 | docker-compose up --build -d 9 | yarn run start 10 | 11 | reset: 12 | docker-compose down --volumes 13 | 14 | rebuild: 15 | reset 16 | docker-compose build --no-cache 17 | 18 | install: 19 | docker exec ${CURDIR}_wordpress_1 wp core install --url='localhost:8888' --title='Example' --admin_user='admin' --admin_password='root' --admin_email='admin@example.com' 20 | 21 | activate: 22 | docker exec ${CURDIR}_wordpress_1 wp plugin activate disqus 23 | 24 | js: 25 | yarn run build 26 | 27 | docker-test: 28 | docker-compose -f docker-compose.test.yml rm -f -v 29 | docker-compose -f docker-compose.test.yml build 30 | docker-compose -f docker-compose.test.yml up --abort-on-container-exit 31 | 32 | dist: 33 | yarn run build 34 | rm -f disqus.zip 35 | zip -r disqus.zip disqus 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Disqus WordPress Plugin 2 | 3 | ## Local Testing 4 | 5 | There's a Docker configuration to bootstrap a local WordPress installation. To start it: 6 | 7 | - You'll need both [Docker](https://docs.docker.com/install/) and [yarn](https://yarnpkg.com/) installed on your local machine. 8 | - From the root directory, run `$ yarn` to install the frontend dependencies. 9 | - Once that's done, run `$ make run` to build the docker image. 10 | - Install wordpress with the default values by running `$ make install`. 11 | - Activate the Disqus plugin using `$ make activate`. 12 | - Alternatively you can now complete configuration by going to: http://localhost:8888/wp-admin/install.php 13 | - Otherwise you can continue to: http://localhost:8888/ 14 | 15 | ### Unit tests 16 | 17 | To run unit tests locally: 18 | 19 | - Run `$ make docker-test`. 20 | 21 | This will run all frontend (Jest) and backend (PHPUnit) tests. 22 | 23 | ## Installing to existing WordPress site 24 | 25 | To install the plugin on an existing WordPress site, you'll need to build the frontend and create a .zip file: 26 | 27 | - Make sure you have [yarn](https://yarnpkg.com/) installed on your local machine. 28 | - From the root directory, run `$ make dist`. 29 | - From your WordPress plugin page, select Plugins->Add New->Upload Plugin and choose the `disqus.zip` file you created. 30 | - Activate the plugin and then install using the instructions in the plugin. 31 | 32 | ## Custom Filters 33 | 34 | ### dsq_can_load 35 | 36 | You can override when Disqus loads on a certain page using the `dsq_can_load` filter. By default Disqus won't load scripts on: 37 | - On RSS feed pages 38 | - If the `shortname` is missing from the configuration 39 | 40 | Additionally Disqus will not load the embed.js script: 41 | - On pages that are not 'post' or 'page' types 42 | - Posts or pages that have comment turned off 43 | - Draft posts 44 | 45 | The filter will pass one argument, the `$script_name` enumeration, which can be: 46 | - `'count'` — The count.js script file for showing comment counts next to a link 47 | - `'embed'` — The commenting embed.js file 48 | 49 | In this example, you can add a filter which disables the comment count script, while still allowing the comments embed to load: 50 | 51 | ```php 52 | function filter_dsq_can_load( $script_name ) { 53 | // $script_name is either 'count' or 'embed'. 54 | if ( 'count' === $script_name ) { 55 | return false; 56 | } 57 | 58 | return true; 59 | } 60 | add_filter( 'dsq_can_load', 'filter_dsq_can_load' ); 61 | ``` 62 | -------------------------------------------------------------------------------- /assets/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disqus/disqus-wordpress-plugin/9c20fe3a8e5577b2e7ee3094b0c6a8d1af15859a/assets/banner-772x250.png -------------------------------------------------------------------------------- /assets/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disqus/disqus-wordpress-plugin/9c20fe3a8e5577b2e7ee3094b0c6a8d1af15859a/assets/icon-256x256.png -------------------------------------------------------------------------------- /assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /assets/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disqus/disqus-wordpress-plugin/9c20fe3a8e5577b2e7ee3094b0c6a8d1af15859a/assets/screenshot-1.png -------------------------------------------------------------------------------- /assets/screenshot-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disqus/disqus-wordpress-plugin/9c20fe3a8e5577b2e7ee3094b0c6a8d1af15859a/assets/screenshot-10.png -------------------------------------------------------------------------------- /assets/screenshot-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disqus/disqus-wordpress-plugin/9c20fe3a8e5577b2e7ee3094b0c6a8d1af15859a/assets/screenshot-11.png -------------------------------------------------------------------------------- /assets/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disqus/disqus-wordpress-plugin/9c20fe3a8e5577b2e7ee3094b0c6a8d1af15859a/assets/screenshot-2.png -------------------------------------------------------------------------------- /assets/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disqus/disqus-wordpress-plugin/9c20fe3a8e5577b2e7ee3094b0c6a8d1af15859a/assets/screenshot-3.png -------------------------------------------------------------------------------- /assets/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disqus/disqus-wordpress-plugin/9c20fe3a8e5577b2e7ee3094b0c6a8d1af15859a/assets/screenshot-4.png -------------------------------------------------------------------------------- /assets/screenshot-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disqus/disqus-wordpress-plugin/9c20fe3a8e5577b2e7ee3094b0c6a8d1af15859a/assets/screenshot-5.png -------------------------------------------------------------------------------- /assets/screenshot-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disqus/disqus-wordpress-plugin/9c20fe3a8e5577b2e7ee3094b0c6a8d1af15859a/assets/screenshot-6.png -------------------------------------------------------------------------------- /assets/screenshot-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disqus/disqus-wordpress-plugin/9c20fe3a8e5577b2e7ee3094b0c6a8d1af15859a/assets/screenshot-7.png -------------------------------------------------------------------------------- /assets/screenshot-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disqus/disqus-wordpress-plugin/9c20fe3a8e5577b2e7ee3094b0c6a8d1af15859a/assets/screenshot-8.png -------------------------------------------------------------------------------- /assets/screenshot-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disqus/disqus-wordpress-plugin/9c20fe3a8e5577b2e7ee3094b0c6a8d1af15859a/assets/screenshot-9.png -------------------------------------------------------------------------------- /bin/deploy.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # Based on https://github.com/GaryJones/wordpress-plugin-git-flow-svn-deploy for instructions and credits. 3 | 4 | # Set up some default values. Feel free to change these in your own script 5 | PLUGINSLUG="disqus" 6 | CURRENTDIR=`pwd` 7 | SVNPATH="$CURRENTDIR/tmp" 8 | PLUGINDIR="$CURRENTDIR/$PLUGINSLUG" 9 | MAINFILE="$PLUGINSLUG.php" 10 | 11 | echo "Slug: $PLUGINSLUG" 12 | echo "Temp checkout path: $SVNPATH" 13 | echo "Remote SVN repo: $SVNURL" 14 | echo "SVN username: $SVNUSER" 15 | echo "Plugin directory: $PLUGINDIR" 16 | echo "Main file: $MAINFILE" 17 | echo 18 | 19 | # Let's begin... 20 | echo ".........................................." 21 | echo 22 | echo "Preparing to deploy WordPress plugin" 23 | echo 24 | echo ".........................................." 25 | echo 26 | 27 | # Make sure the SVN temporary folder is removed 28 | rm -fr $SVNPATH/ 29 | 30 | # Check version in readme.txt is the same as plugin file after translating both to unix line breaks to work around grep's failure to identify mac line breaks 31 | PLUGINVERSION=`grep "Version:" $PLUGINDIR/$MAINFILE | awk -F' ' '{print $NF}' | tr -d '\r'` 32 | echo "$MAINFILE version: $PLUGINVERSION" 33 | READMEVERSION=`grep "^Stable tag:" $PLUGINDIR/README.txt | awk -F' ' '{print $NF}' | tr -d '\r'` 34 | echo "README.txt version: $READMEVERSION" 35 | 36 | # Check the version in package.json and make sure it's also the same 37 | PACKAGEVERSION=`cat $CURRENTDIR/package.json | jq -r '.version'` 38 | echo "package.json version: $PACKAGEVERSION" 39 | 40 | if [ "$READMEVERSION" = "trunk" ]; then 41 | echo "Version in readme.txt & $MAINFILE don't match, but Stable tag is trunk. Continuing..." 42 | elif [ "$PLUGINVERSION" != "$READMEVERSION" ]; then 43 | echo "Version in readme.txt & $MAINFILE don't match. Exiting..." 44 | exit 1; 45 | elif [ "$PACKAGEVERSION" != "$PLUGINVERSION" ]; then 46 | echo "Version in package.json & $MAINFILE don't match. Exiting..." 47 | exit 1; 48 | elif [ "$PLUGINVERSION" = "$READMEVERSION" ]; then 49 | echo "Versions match in readme.txt, package.json and $MAINFILE. Continuing..." 50 | fi 51 | 52 | echo 53 | echo "Creating local copy of SVN repo trunk ..." 54 | svn checkout $SVNURL $SVNPATH --non-interactive --no-auth-cache --username=$SVNUSER --password=$SVNPASS --depth immediates 55 | svn update --quiet $SVNPATH/trunk --set-depth infinity --non-interactive --no-auth-cache --username=$SVNUSER --password=$SVNPASS 56 | 57 | # Check latest version tag on SVN and see if this version is a duplicate 58 | cd $SVNPATH 59 | TAGREVISION=`svn info $SVNURL/tags/$PLUGINVERSION --non-interactive --no-auth-cache --username=$SVNUSER --password=$SVNPASS | grep Revision | tr -d 'Revison: '` 60 | 61 | if [ -z "$TAGREVISION" ]; then 62 | echo "No tag for $PLUGINVERSION yet. Continuing..." 63 | else 64 | echo "Not deploying to wordpress.org. A tag exists with the version $PLUGINVERSION. Update the plugin version before deploying to trigger an update on wordpress.org. Exiting." 65 | exit 0; 66 | fi 67 | 68 | cd .. 69 | 70 | echo "Ignoring GitHub specific files" 71 | svn propset svn:ignore "README.md 72 | Thumbs.db 73 | .github/* 74 | .git 75 | .gitattributes 76 | .gitignore" "$SVNPATH/trunk/" 77 | 78 | # Make sure assets and trunk files are clean 79 | echo "Removing all files from /assets and /trunk" 80 | rm -rf $SVNPATH/trunk/* 81 | rm -rf $SVNPATH/assets/* 82 | 83 | # Move the folder with the plugin files into trunk 84 | echo "Copying plugin files into trunk" 85 | cp -r $PLUGINDIR/* $SVNPATH/trunk/ 86 | 87 | # Support for the /assets folder on the .org repo. 88 | echo "Copying asset files" 89 | # Make the directory if it doesn't already exist 90 | mkdir -p $SVNPATH/assets/ 91 | cp -r $CURRENTDIR/assets/* $SVNPATH/assets/ 92 | 93 | svn add --force $SVNPATH/assets/ 94 | 95 | echo "Changing directory to SVN and committing to trunk" 96 | cd $SVNPATH/trunk/ 97 | # Delete all files that should not now be added. 98 | svn status | grep -v "^.[ \t]*\..*" | grep "^\!" | awk '{print $2"@"}' | xargs svn del 99 | # Add all new files that are not set to be ignored 100 | svn status | grep -v "^.[ \t]*\..*" | grep "^?" | awk '{print $2"@"}' | xargs svn add 101 | svn commit --non-interactive --no-auth-cache --username=$SVNUSER --password=$SVNPASS -m "Preparing for $PLUGINVERSION release" 102 | 103 | echo "Updating WordPress plugin repo assets and committing" 104 | cd $SVNPATH/assets/ 105 | # Delete all new files that are not set to be ignored 106 | svn status | grep -v "^.[ \t]*\..*" | grep "^\!" | awk '{print $2"@"}' | xargs svn del 107 | # Add all new files that are not set to be ignored 108 | svn status | grep -v "^.[ \t]*\..*" | grep "^?" | awk '{print $2"@"}' | xargs svn add 109 | svn update --non-interactive --no-auth-cache --username=$SVNUSER --password=$SVNPASS --accept mine-full $SVNPATH/assets/* 110 | svn commit --non-interactive --no-auth-cache --username=$SVNUSER --password=$SVNPASS -m "Updating assets" 111 | 112 | echo "Creating new SVN tag and committing it" 113 | cd $SVNPATH 114 | svn update --quiet $SVNPATH/tags/$PLUGINVERSION --non-interactive --no-auth-cache --username=$SVNUSER --password=$SVNPASS 115 | svn copy --quiet trunk/ tags/$PLUGINVERSION/ 116 | # Remove trunk folder from tag directory 117 | svn delete --force --quiet $SVNPATH/tags/$PLUGINVERSION/trunk 118 | cd $SVNPATH/tags/$PLUGINVERSION 119 | svn commit --non-interactive --no-auth-cache --username=$SVNUSER --password=$SVNPASS -m "Tagging version $PLUGINVERSION" 120 | 121 | echo "Removing temporary directory $SVNPATH" 122 | cd $SVNPATH 123 | cd .. 124 | rm -fr $SVNPATH/ 125 | 126 | echo "*** FIN ***" 127 | -------------------------------------------------------------------------------- /bin/install-wp-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | DB_HOST=${4-localhost} 12 | WP_VERSION=${5-latest} 13 | SKIP_DB_CREATE=${6-false} 14 | 15 | TMPDIR=${TMPDIR-/tmp} 16 | TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//") 17 | WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib} 18 | WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/} 19 | 20 | download() { 21 | if [ `which curl` ]; then 22 | curl -s "$1" > "$2"; 23 | elif [ `which wget` ]; then 24 | wget -nv -O "$2" "$1" 25 | fi 26 | } 27 | 28 | if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then 29 | WP_TESTS_TAG="branches/$WP_VERSION" 30 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then 31 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then 32 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x 33 | WP_TESTS_TAG="tags/${WP_VERSION%??}" 34 | else 35 | WP_TESTS_TAG="tags/$WP_VERSION" 36 | fi 37 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 38 | WP_TESTS_TAG="trunk" 39 | else 40 | # http serves a single offer, whereas https serves multiple. we only want one 41 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json 42 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json 43 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 44 | if [[ -z "$LATEST_VERSION" ]]; then 45 | echo "Latest WordPress version could not be found" 46 | exit 1 47 | fi 48 | WP_TESTS_TAG="tags/$LATEST_VERSION" 49 | fi 50 | 51 | set -ex 52 | 53 | install_wp() { 54 | 55 | if [ -d $WP_CORE_DIR ]; then 56 | return; 57 | fi 58 | 59 | mkdir -p $WP_CORE_DIR 60 | 61 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 62 | mkdir -p $TMPDIR/wordpress-nightly 63 | download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip 64 | unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/ 65 | mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR 66 | else 67 | if [ $WP_VERSION == 'latest' ]; then 68 | local ARCHIVE_NAME='latest' 69 | elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then 70 | # https serves multiple offers, whereas http serves single. 71 | download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json 72 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then 73 | # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x 74 | LATEST_VERSION=${WP_VERSION%??} 75 | else 76 | # otherwise, scan the releases and get the most up to date minor version of the major release 77 | local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'` 78 | LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1) 79 | fi 80 | if [[ -z "$LATEST_VERSION" ]]; then 81 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 82 | else 83 | local ARCHIVE_NAME="wordpress-$LATEST_VERSION" 84 | fi 85 | else 86 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 87 | fi 88 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz 89 | tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR 90 | fi 91 | 92 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php 93 | } 94 | 95 | install_test_suite() { 96 | # portable in-place argument for both GNU sed and Mac OSX sed 97 | if [[ $(uname -s) == 'Darwin' ]]; then 98 | local ioption='-i .bak' 99 | else 100 | local ioption='-i' 101 | fi 102 | 103 | # set up testing suite if it doesn't yet exist 104 | if [ ! -d $WP_TESTS_DIR ]; then 105 | # set up testing suite 106 | mkdir -p $WP_TESTS_DIR 107 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes 108 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data 109 | fi 110 | 111 | if [ ! -f wp-tests-config.php ]; then 112 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php 113 | # remove all forward slashes in the end 114 | WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") 115 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php 116 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php 117 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php 118 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php 119 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php 120 | fi 121 | 122 | } 123 | 124 | install_db() { 125 | 126 | if [ ${SKIP_DB_CREATE} = "true" ]; then 127 | return 0 128 | fi 129 | 130 | # parse DB_HOST for port or socket references 131 | local PARTS=(${DB_HOST//\:/ }) 132 | local DB_HOSTNAME=${PARTS[0]}; 133 | local DB_SOCK_OR_PORT=${PARTS[1]}; 134 | local EXTRA="" 135 | 136 | if ! [ -z $DB_HOSTNAME ] ; then 137 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then 138 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 139 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 140 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 141 | elif ! [ -z $DB_HOSTNAME ] ; then 142 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 143 | fi 144 | fi 145 | 146 | # create database 147 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 148 | } 149 | 150 | install_wp 151 | install_test_suite 152 | install_db 153 | -------------------------------------------------------------------------------- /bin/publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Retrieve the current version number and bump the versions of all required files by the specified amount, create a git version tag, and push the changes to master, triggering a new deploy. 4 | # Based on https://gist.github.com/andyexeter/da932c9644d832e3be6706d20d539ff7 5 | 6 | # Usage: ./bin/publish.sh - Increments the relevant version part by one. 7 | 8 | set -e 9 | 10 | 11 | function bump() { 12 | echo -n "Updating $1..." 13 | tmp_file=$(mktemp) 14 | rm -f "$tmp_file" 15 | sed -i "" "s/$2/$3/1w $tmp_file" $1 16 | if [ -s "$tmp_file" ]; then 17 | echo "Done" 18 | else 19 | echo "Nothing to change" 20 | fi 21 | rm -f "$tmp_file" 22 | } 23 | 24 | function confirm() { 25 | read -r -p "$@ [Y/n]: " confirm 26 | 27 | case "$confirm" in 28 | [Nn][Oo]|[Nn]) 29 | echo "Aborting." 30 | exit 31 | ;; 32 | esac 33 | } 34 | 35 | function check_branch() { 36 | current_branch=$(git rev-parse --abbrev-ref HEAD) 37 | if [[ "$current_branch" != "master" ]]; then 38 | echo "This script can only be executed from the 'master' branch." 39 | echo "Aborting." 40 | exit 41 | fi 42 | } 43 | 44 | function check_commits() { 45 | staged_commits=$(git log origin/master..HEAD --pretty=format:"%h - %s") 46 | commit_count=$(echo $log_str | wc -l) 47 | if [[ $commit_count -gt 0 ]]; then 48 | printf "The following staged commits will also be pushed to master.\n$staged_commits\n" 49 | confirm "Are you sure?" 50 | fi 51 | } 52 | 53 | if [ "$1" == "" ]; then 54 | echo >&2 "No version type provided. Aborting." 55 | exit 1 56 | fi 57 | 58 | if [ "$1" == "major" ] || [ "$1" == "minor" ] || [ "$1" == "patch" ]; then 59 | 60 | # Get the current plugin version from the package.json 61 | current_version=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[[:space:]]') 62 | 63 | IFS='.' read -a version_parts <<< "$current_version" 64 | 65 | major=${version_parts[0]} 66 | minor=${version_parts[1]} 67 | patch=${version_parts[2]} 68 | 69 | case "$1" in 70 | "major") 71 | major=$((major + 1)) 72 | minor=0 73 | patch=0 74 | ;; 75 | "minor") 76 | minor=$((minor + 1)) 77 | patch=0 78 | ;; 79 | "patch") 80 | patch=$((patch + 1)) 81 | ;; 82 | esac 83 | new_version="$major.$minor.$patch" 84 | else 85 | if [ "$2" == "" ]; then 86 | echo >&2 "No 'to' version set. Aborting." 87 | exit 1 88 | fi 89 | current_version="$1" 90 | new_version="$2" 91 | fi 92 | 93 | if ! [[ "$new_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 94 | echo >&2 "'to' version doesn't look like a valid semver version tag (e.g: 1.2.3). Aborting." 95 | exit 1 96 | fi 97 | 98 | confirm "Bump version number from $current_version to $new_version?" 99 | 100 | confirm "Have you updated the Changelogs in README.txt and docs/CHANGELOG.md?" 101 | 102 | # Make sure this is only executed from the master branch 103 | check_branch 104 | 105 | # Confirm commits that will be pushed with version changes 106 | check_commits 107 | 108 | # Update package.json version 109 | bump package.json "\"version\": \"$current_version\"" "\"version\": \"$new_version\"" 110 | 111 | # Update README.txt version 112 | bump disqus/README.txt "Stable tag: $current_version" "Stable tag: $new_version" 113 | 114 | # Update disqus.php versions 115 | bump disqus/disqus.php "Version: $current_version" "Version: $new_version" 116 | bump disqus/disqus.php "$DISQUSVERSION = '$current_version'" "$DISQUSVERSION = '$new_version'" 117 | 118 | # Create commit with version updates 119 | echo "Committing version changes: \"Update plugin version to v$new_version\"" 120 | git add package.json disqus/README.txt disqus/disqus.php 121 | git commit -m "Update plugin version to v$new_version" 122 | 123 | # Create a new git version tag 124 | echo "Adding new version tag: $new_version" 125 | git tag "$new_version" 126 | 127 | # Push version changes and version tag to master 128 | echo "Pushing version changes to master" 129 | git push --tags origin master 130 | -------------------------------------------------------------------------------- /bin/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Run PHPUnit with and without multisite enabled to prevent the need for 3 | # multiple CI builds 4 | 5 | # Run single-site unit tests 6 | export WP_MULTISITE=0 7 | ./vendor/bin/phpunit --exclude-group=ms-required 8 | 9 | # Run Multisite unit tests 10 | export WP_MULTISITE=1 11 | ./vendor/bin/phpunit --exclude-group=ms-excluded 12 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "disqus/disqus-wordpress-plugin", 3 | "description": "WordPress plugin for Disqus", 4 | "keywords": [ 5 | "wordpress", 6 | "disqus", 7 | "comments" 8 | ], 9 | "homepage": "https://disqus.com/", 10 | "license": "GPL-2.0+", 11 | "authors": [ 12 | { 13 | "name": "Ryan Valentin", 14 | "email": "ryan@disqus.com" 15 | } 16 | ], 17 | "type": "wordpress-plugin", 18 | "require": { 19 | "php": ">=5.4.0", 20 | "composer/installers": "v1.9.0" 21 | }, 22 | "require-dev": { 23 | "squizlabs/php_codesniffer": "^3.0.2", 24 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1", 25 | "wp-coding-standards/wpcs": "^0.14.0", 26 | "phpunit/phpunit": "^8", 27 | "yoast/phpunit-polyfills": "^1.1" 28 | }, 29 | "config": { 30 | "allow-plugins": { 31 | "composer/installers": true, 32 | "dealerdirect/phpcodesniffer-composer-installer": true 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /disqus/admin/index.php: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /disqus/disqus.php: -------------------------------------------------------------------------------- 1 | run(); 76 | 77 | } 78 | run_disqus(); 79 | -------------------------------------------------------------------------------- /disqus/includes/class-disqus-api-service.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Disqus_Api_Service { 20 | 21 | const DISQUS_API_BASE = 'https://disqus.com/api/3.0/'; 22 | 23 | /** 24 | * The Disqus API secret key. 25 | * 26 | * @since 3.0 27 | * @access private 28 | * @var string $api_secret The Disqus API secret key. 29 | */ 30 | private $api_secret; 31 | 32 | /** 33 | * The Disqus access token for authentication. 34 | * 35 | * @since 3.0 36 | * @access private 37 | * @var string $access_token The Disqus access token for authentication. 38 | */ 39 | private $access_token; 40 | 41 | /** 42 | * Initialize the class and set its properties. 43 | * 44 | * @since 3.0 45 | * @param string $api_secret The Disqus API secret key. 46 | * @param string $access_token The admin access token associated with the $api_secret. 47 | */ 48 | public function __construct( $api_secret, $access_token = '' ) { 49 | $this->api_secret = $api_secret; 50 | $this->access_token = $access_token; 51 | } 52 | 53 | /** 54 | * Makes a GET request to the Disqus API. 55 | * 56 | * @since 3.0 57 | * @param string $endpoint The Disqus API secret key. 58 | * @param array $params The params to be appended to the query. 59 | * @return mixed The response data. 60 | */ 61 | public function api_get( $endpoint, $params ) { 62 | $api_url = Disqus_Api_Service::DISQUS_API_BASE . $endpoint . '.json?' 63 | . 'api_secret=' . $this->api_secret 64 | . '&access_token=' . $this->access_token; 65 | 66 | foreach ( $params as $key => $values_array ) { 67 | if ( ! is_array( $values_array ) ) { 68 | $values_array = array( $values_array ); 69 | } 70 | 71 | foreach ( $values_array as $value ) { 72 | $api_url .= '&' . $key . '=' . urlencode( $value ); 73 | } 74 | } 75 | 76 | $dsq_response = wp_remote_get( $api_url, array( 77 | 'headers' => array( 78 | 'Referer' => '', // Unset referer so we can use secret key. 79 | ), 80 | ) ); 81 | 82 | return $this->get_response_body( $dsq_response ); 83 | } 84 | 85 | /** 86 | * Makes a POST request to the Disqus API. 87 | * 88 | * @since 3.0 89 | * @param string $endpoint The Disqus API secret key. 90 | * @param array $params The params to be added to the body. 91 | * @return mixed The response data. 92 | */ 93 | public function api_post( $endpoint, $params ) { 94 | $api_url = Disqus_Api_Service::DISQUS_API_BASE . $endpoint . '.json?' 95 | . 'api_secret=' . $this->api_secret 96 | . '&access_token=' . $this->access_token; 97 | 98 | $dsq_response = wp_remote_post( $api_url, array( 99 | 'body' => $params, 100 | 'headers' => array( 101 | 'Referer' => '', // Unset referer so we can use secret key. 102 | ), 103 | 'method' => 'POST', 104 | ) ); 105 | 106 | return $this->get_response_body( $dsq_response ); 107 | } 108 | 109 | /** 110 | * Checks the type of response and returns and object variable with the Disqus response. 111 | * 112 | * @since 3.0 113 | * @param WP_Error|array $response The remote response. 114 | * @return mixed The response body in Disqus API format. 115 | */ 116 | private function get_response_body( $response ) { 117 | if ( is_wp_error( $response ) ) { 118 | // The WP_Error class has no way of getting the response body, so we have to 119 | // emulate the Disqus API error format for downstream compatibility. 120 | $error_message = $response->get_error_message(); 121 | $response = new StdClass(); 122 | $response->code = 2; 123 | $response->response = $error_message; 124 | } else { 125 | $response = json_decode( $response['body'] ); 126 | } 127 | 128 | return $response; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /disqus/includes/class-disqus-deactivator.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class Disqus_Deactivator { 23 | 24 | /** 25 | * Short Description. (use period) 26 | * 27 | * Long Description. 28 | * 29 | * @since 3.0 30 | */ 31 | public static function deactivate() { 32 | // TODO: Call Disqus to disable syncing webhooks? 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /disqus/includes/class-disqus-loader.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class Disqus_Loader { 24 | 25 | /** 26 | * The array of actions registered with WordPress. 27 | * 28 | * @since 3.0 29 | * @access protected 30 | * @var array $actions The actions registered with WordPress to fire when the plugin loads. 31 | */ 32 | protected $actions; 33 | 34 | /** 35 | * The array of filters registered with WordPress. 36 | * 37 | * @since 3.0 38 | * @access protected 39 | * @var array $filters The filters registered with WordPress to fire when the plugin loads. 40 | */ 41 | protected $filters; 42 | 43 | /** 44 | * Initialize the collections used to maintain the actions and filters. 45 | * 46 | * @since 3.0 47 | */ 48 | public function __construct() { 49 | 50 | $this->actions = array(); 51 | $this->filters = array(); 52 | 53 | } 54 | 55 | /** 56 | * Add a new action to the collection to be registered with WordPress. 57 | * 58 | * @since 3.0 59 | * @param string $hook The name of the WordPress action that is being registered. 60 | * @param object $component A reference to the instance of the object on which the action is defined. 61 | * @param string $callback The name of the function definition on the $component. 62 | * @param int Optional $priority The priority at which the function should be fired. 63 | * @param int Optional $accepted_args The number of arguments that should be passed to the $callback. 64 | */ 65 | public function add_action( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) { 66 | $this->actions = $this->add( $this->actions, $hook, $component, $callback, $priority, $accepted_args ); 67 | } 68 | 69 | /** 70 | * Add a new filter to the collection to be registered with WordPress. 71 | * 72 | * @since 3.0 73 | * @param string $hook The name of the WordPress filter that is being registered. 74 | * @param object $component A reference to the instance of the object on which the filter is defined. 75 | * @param string $callback The name of the function definition on the $component. 76 | * @param int Optional $priority The priority at which the function should be fired. 77 | * @param int Optional $accepted_args The number of arguments that should be passed to the $callback. 78 | */ 79 | public function add_filter( $hook, $component, $callback, $priority = 10, $accepted_args = 1 ) { 80 | $this->filters = $this->add( $this->filters, $hook, $component, $callback, $priority, $accepted_args ); 81 | } 82 | 83 | /** 84 | * A utility function that is used to register the actions and hooks into a single 85 | * collection. 86 | * 87 | * @since 3.0 88 | * @access private 89 | * @param array $hooks The collection of hooks that is being registered (that is, actions or filters). 90 | * @param string $hook The name of the WordPress filter that is being registered. 91 | * @param object $component A reference to the instance of the object on which the filter is defined. 92 | * @param string $callback The name of the function definition on the $component. 93 | * @param int Optional $priority The priority at which the function should be fired. 94 | * @param int Optional $accepted_args The number of arguments that should be passed to the $callback. 95 | * @return type The collection of actions and filters registered with WordPress. 96 | */ 97 | private function add( $hooks, $hook, $component, $callback, $priority, $accepted_args ) { 98 | 99 | $hooks[] = array( 100 | 'hook' => $hook, 101 | 'component' => $component, 102 | 'callback' => $callback, 103 | 'priority' => $priority, 104 | 'accepted_args' => $accepted_args, 105 | ); 106 | 107 | return $hooks; 108 | 109 | } 110 | 111 | /** 112 | * Register the filters and actions with WordPress. 113 | * 114 | * @since 3.0 115 | */ 116 | public function run() { 117 | 118 | foreach ( $this->filters as $hook ) { 119 | add_filter( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] ); 120 | } 121 | 122 | foreach ( $this->actions as $hook ) { 123 | add_action( $hook['hook'], array( $hook['component'], $hook['callback'] ), $hook['priority'], $hook['accepted_args'] ); 124 | } 125 | 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /disqus/includes/index.php: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 | 21 | 25 | 28 | -------------------------------------------------------------------------------- /disqus/public/partials/disqus-public-sso-login-profile.php: -------------------------------------------------------------------------------- 1 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /disqus/rest-api/index.php: -------------------------------------------------------------------------------- 1 | 32 | bash -e -x -c " 33 | cd /usr/src/app 34 | 35 | # We need to re-run these because we're mapping our local copy 36 | # inside the container 37 | echo '=== Installing dependencies ===' 38 | composer install 39 | yarnpkg install 40 | 41 | echo '=== Waiting for MySQL to be ready ===' 42 | dockerize -wait tcp://db:3306 43 | 44 | echo '=== Configuring WordPress ===' 45 | bin/install-wp-tests.sh wordpress_test root wordpress db latest 46 | 47 | echo '=== Running frontend tests ===' 48 | yarnpkg test 49 | 50 | echo '=== Running backend tests ===' 51 | bin/test.sh 52 | " 53 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | db: 5 | image: mysql:5.7 6 | volumes: 7 | - db_data:/var/lib/mysql 8 | restart: always 9 | environment: 10 | MYSQL_ROOT_PASSWORD: wordpress 11 | MYSQL_DATABASE: wordpress 12 | MYSQL_USER: wordpress 13 | MYSQL_PASSWORD: wordpress 14 | 15 | wordpress: 16 | build: . 17 | depends_on: 18 | - db 19 | ports: 20 | - '8888:80' 21 | restart: always 22 | volumes: 23 | # Maps the Disqus plugin folder locally to the container filesystem 24 | - "./disqus:/var/www/html/wp-content/plugins/disqus" 25 | environment: 26 | WORDPRESS_DB_HOST: db:3306 27 | WORDPRESS_DB_PASSWORD: wordpress 28 | WORDPRESS_DEBUG: debug 29 | 30 | volumes: 31 | db_data: 32 | -------------------------------------------------------------------------------- /frontend/src/ts/DisqusApi.ts: -------------------------------------------------------------------------------- 1 | import { Moment } from "moment"; 2 | 3 | export class DisqusApi { 4 | static get instance() { 5 | return disqusApi; 6 | } 7 | 8 | // tslint:disable:variable-name 9 | private _apiKey: string; 10 | private _accessToken: string; 11 | private _forum: string; 12 | // tslint:enable:variable-name 13 | 14 | public get apiKey(): string { 15 | return this._apiKey; 16 | } 17 | 18 | public get accessToken(): string { 19 | return this._accessToken; 20 | } 21 | 22 | public get forum(): string { 23 | return this._forum; 24 | } 25 | 26 | public configure(apiKey: string, accessToken: string, forum: string) { 27 | this._apiKey = apiKey; 28 | this._accessToken = accessToken; 29 | this._forum = forum; 30 | } 31 | 32 | public createImport( 33 | xmlContent: string, 34 | filename: string, 35 | onLoad: EventListenerOrEventListenerObject, 36 | ): XMLHttpRequest { 37 | const formData: FormData = new FormData(); 38 | formData.append('upload', new Blob([xmlContent], { type: 'text/xml' }), filename); 39 | formData.append('sourceType', '0'); 40 | formData.append('forum', this.forum); 41 | 42 | return this.post('imports/create', formData, onLoad); 43 | } 44 | 45 | public listPostsForForum( 46 | cursor: string, 47 | startDate: Moment, 48 | endDate: Moment, 49 | limit: number, 50 | onLoad: EventListenerOrEventListenerObject, 51 | ): XMLHttpRequest { 52 | const query: string = [ 53 | `start=${startDate.toISOString()}`, 54 | `end=${endDate.toISOString()}`, 55 | `forum=${this.forum}`, 56 | 'related=thread', 57 | 'order=asc', 58 | `limit=${Math.min(Math.max(limit, 1), 100)}`, 59 | `cursor=${cursor || ''}`, 60 | ].join('&'); 61 | 62 | return this.get('posts/list', query, onLoad); 63 | } 64 | 65 | private get(path: string, query: string, onLoad: EventListenerOrEventListenerObject): XMLHttpRequest { 66 | if (!this.apiKey) 67 | return null; 68 | 69 | const XHR = new XMLHttpRequest(); 70 | XHR.open( 71 | 'GET', 72 | `https://disqus.com/api/3.0/${path}.json?api_key=${this.apiKey}&access_token=${this.accessToken}&${query}`, 73 | ); 74 | XHR.addEventListener('load', onLoad); 75 | XHR.send(); 76 | 77 | return XHR; 78 | } 79 | 80 | private post(path: string, data: FormData, onLoad: EventListenerOrEventListenerObject): XMLHttpRequest { 81 | if (!this.apiKey) 82 | return null; 83 | 84 | data.append('api_key', this.apiKey); 85 | data.append('access_token', this.accessToken); 86 | 87 | const XHR = new XMLHttpRequest(); 88 | XHR.open('POST', `https://disqus.com/api/3.0/${path}.json`); 89 | XHR.addEventListener('load', onLoad); 90 | XHR.send(data); 91 | 92 | return XHR; 93 | } 94 | } 95 | 96 | const disqusApi: DisqusApi = new DisqusApi(); 97 | -------------------------------------------------------------------------------- /frontend/src/ts/REST_OPTIONS.ts: -------------------------------------------------------------------------------- 1 | import { IDisqusWordpressWindow } from './reducers/AdminState'; 2 | 3 | const WIN = window as IDisqusWordpressWindow; 4 | const REST_OPTIONS = WIN.DISQUS_WP && WIN.DISQUS_WP.rest; 5 | 6 | export default REST_OPTIONS; 7 | -------------------------------------------------------------------------------- /frontend/src/ts/WordPressCommentExporter.ts: -------------------------------------------------------------------------------- 1 | import * as Redux from 'redux'; 2 | import { updateExportPostLogAction } from './actions'; 3 | import { DisqusApi } from './DisqusApi'; 4 | import { ExportLogStaus } from './reducers/AdminState'; 5 | import { WordPressRestApi } from './WordPressRestApi'; 6 | 7 | const POSTS_PER_PAGE: number = 10; 8 | 9 | class WordPressCommentExporter { 10 | private currentPage: number; 11 | private dispatch: Redux.Dispatch; 12 | 13 | constructor(dispatch: Redux.Dispatch) { 14 | this.dispatch = dispatch; 15 | this.currentPage = 1; 16 | 17 | this.handleDisqusImportResponse = this.handleDisqusImportResponse.bind(this); 18 | this.handleExportPostResponse = this.handleExportPostResponse.bind(this); 19 | this.handlePostsResponse = this.handlePostsResponse.bind(this); 20 | } 21 | 22 | public startExportPosts(): XMLHttpRequest { 23 | return WordPressRestApi.instance.wordpressRestGet( 24 | 'posts', 25 | `per_page=${POSTS_PER_PAGE}&page=${this.currentPage}`, 26 | this.handlePostsResponse, 27 | ); 28 | } 29 | 30 | public exportPost(post: any): void { 31 | WordPressRestApi.instance.pluginRestPost( 32 | 'export/post', 33 | { postId: post.id }, 34 | this.handleExportPostResponse.bind(null, post), 35 | ); 36 | } 37 | 38 | public dispatchComplete(post: any, numComments: number): void { 39 | this.dispatch(updateExportPostLogAction({ 40 | error: null, 41 | id: post.id, 42 | link: post.link, 43 | numComments, 44 | status: ExportLogStaus.complete, 45 | title: post.title.rendered, 46 | })); 47 | } 48 | 49 | public dispatchError(post: any, error: string): void { 50 | this.dispatch(updateExportPostLogAction({ 51 | error, 52 | id: post.id, 53 | link: post.link, 54 | numComments: null, 55 | status: ExportLogStaus.failed, 56 | title: post.title.rendered, 57 | })); 58 | } 59 | 60 | public handleDisqusImportResponse(post: any, exportPostResponse: any, xhr: Event): void { 61 | let jsonObject = null; 62 | try { 63 | jsonObject = JSON.parse((xhr.target as XMLHttpRequest).responseText); 64 | } catch (error) { 65 | // Continue 66 | } 67 | 68 | if (!jsonObject) { 69 | this.dispatchError(post, __('Unknown error uploading to the Disqus servers')); 70 | return; 71 | } 72 | 73 | if (jsonObject.code !== 0) { 74 | this.dispatchError(post, jsonObject.response); 75 | return; 76 | } 77 | 78 | this.dispatchComplete(post, exportPostResponse.data.comments.length); 79 | } 80 | 81 | public handleExportPostResponse(post: any, response: any): void { 82 | if (!response || response.code !== 'OK') { 83 | this.dispatchError(post, response.message); 84 | return; 85 | } 86 | 87 | if (!response.data.comments.length) { 88 | this.dispatchComplete(post, response.data.comments.length); 89 | return; 90 | } 91 | 92 | const wxr = response.data.wxr; 93 | DisqusApi.instance.createImport( 94 | wxr.xmlContent, 95 | wxr.filename, 96 | this.handleDisqusImportResponse.bind(null, post, response), 97 | ); 98 | } 99 | 100 | public handlePostsResponse(response: any): void { 101 | if (Array.isArray(response)) { 102 | response.forEach((post: any) => { 103 | this.dispatch(updateExportPostLogAction({ 104 | error: null, 105 | id: post.id, 106 | link: post.link, 107 | numComments: null, 108 | status: ExportLogStaus.pending, 109 | title: post.title.rendered, 110 | })); 111 | this.exportPost(post); 112 | }); 113 | 114 | if (response.length === POSTS_PER_PAGE) { 115 | this.currentPage += 1; 116 | this.startExportPosts(); 117 | } 118 | } 119 | } 120 | } 121 | 122 | export { POSTS_PER_PAGE }; 123 | 124 | export default WordPressCommentExporter; 125 | -------------------------------------------------------------------------------- /frontend/src/ts/WordPressRestApi.ts: -------------------------------------------------------------------------------- 1 | import AdminOptions, { IAdminOptions } from './reducers/AdminOptions'; 2 | import { IDisqusWordpressWindow } from './reducers/AdminState'; 3 | import REST_OPTIONS from './REST_OPTIONS'; 4 | 5 | export interface IRestResponse { 6 | code: string; 7 | data: T; 8 | message: string; 9 | } 10 | 11 | export type RequestData = () => { 12 | path: string; 13 | data: any; 14 | onLoad: (response: IRestResponse) => void; 15 | } 16 | 17 | export type PendingRequests = () => Promise; 18 | 19 | export class WordPressRestApi { 20 | private static current: WordPressRestApi; 21 | 22 | static get instance() { 23 | if (!WordPressRestApi.current) 24 | WordPressRestApi.current = new WordPressRestApi(); 25 | 26 | return WordPressRestApi.current; 27 | } 28 | 29 | static set instance(newInstance) { 30 | WordPressRestApi.current = newInstance; 31 | } 32 | 33 | public pluginRestGet(path: string, onLoad: (response: IRestResponse) => void): XMLHttpRequest { 34 | return this.makeApiRequest( 35 | 'GET', 36 | `${REST_OPTIONS.base}${REST_OPTIONS.disqusBase}${path}`, 37 | null, 38 | (xhr: Event) => { 39 | this.handleResponse((xhr.target as XMLHttpRequest).responseText, onLoad); 40 | }, 41 | ); 42 | } 43 | 44 | public pluginRestPost( 45 | path: string, 46 | data: any, 47 | onLoad: (response: IRestResponse) => void, 48 | ): XMLHttpRequest { 49 | return this.makeApiRequest( 50 | 'POST', 51 | `${REST_OPTIONS.base}${REST_OPTIONS.disqusBase}${path}`, 52 | data && JSON.stringify(data), 53 | (xhr: Event) => { 54 | this.handleResponse((xhr.target as XMLHttpRequest).responseText, onLoad); 55 | }); 56 | } 57 | 58 | public pluginRestPostAsync( 59 | path: string, 60 | data: any, 61 | onLoad: (response: IRestResponse) => void, 62 | ): Promise { 63 | return this.makeApiPromise( 64 | 'POST', 65 | `${REST_OPTIONS.base}${REST_OPTIONS.disqusBase}${path}`, 66 | data && JSON.stringify(data), 67 | (xhr: Event) => { 68 | this.handleResponse((xhr.target as XMLHttpRequest).responseText, onLoad); 69 | }); 70 | } 71 | 72 | public wordpressRestGet(path: string, query: string, onLoad: (response: any) => void) { 73 | return this.makeApiRequest( 74 | 'GET', 75 | `${REST_OPTIONS.base}wp/v2/${path}${REST_OPTIONS.base.indexOf('?') > -1 ? '&' : '?'}${query || ''}`, 76 | null, (xhr: Event) => { 77 | this.handleResponse((xhr.target as XMLHttpRequest).responseText, onLoad); 78 | }); 79 | } 80 | 81 | private makeApiRequest( 82 | method: string, 83 | url: string, 84 | data: any, 85 | onLoad: EventListenerOrEventListenerObject, 86 | ): XMLHttpRequest { 87 | const XHR = new XMLHttpRequest(); 88 | XHR.open(method, url); 89 | XHR.setRequestHeader('Content-type', 'application/json'); 90 | XHR.setRequestHeader('X-WP-Nonce', REST_OPTIONS.nonce); 91 | XHR.addEventListener('load', onLoad); 92 | XHR.send(data); 93 | 94 | return XHR; 95 | } 96 | 97 | private makeApiPromise( 98 | method: string, 99 | url: string, 100 | data: any, 101 | onLoad: EventListenerOrEventListenerObject, 102 | ): Promise { 103 | const XHR = new XMLHttpRequest(); 104 | return new Promise((resolve, reject) => { 105 | XHR.open(method, url); 106 | XHR.setRequestHeader('Content-type', 'application/json'); 107 | XHR.setRequestHeader('X-WP-Nonce', REST_OPTIONS.nonce); 108 | XHR.addEventListener('load', onLoad); 109 | XHR.onreadystatechange = () => { 110 | // Only run if the request is complete 111 | if (XHR.readyState !== 4) { 112 | return; 113 | } 114 | // Process the response 115 | if (XHR.status >= 200 && XHR.status < 300) { 116 | // If successful 117 | resolve(); 118 | } else { 119 | // If failed 120 | reject({ 121 | status: XHR.status, 122 | statusText: XHR.statusText 123 | }); 124 | console.error('Error Status: ', XHR.statusText, '100 character server response preview: ', XHR.responseText.substring(0, 100)); 125 | } 126 | }; 127 | XHR.send(data); 128 | }); 129 | } 130 | 131 | private handleResponse(text: string, callback: (response: IRestResponse) => void) { 132 | let jsonObject = null; 133 | try { 134 | jsonObject = JSON.parse(text); 135 | } catch (error) { 136 | // Continue 137 | } 138 | 139 | callback.call(null, jsonObject); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /frontend/src/ts/actions.ts: -------------------------------------------------------------------------------- 1 | import { IExportPostLog, IMessage, InstallationState } from './reducers/AdminState'; 2 | 3 | export interface IAction { 4 | type: string; 5 | data: any; 6 | } 7 | 8 | export const UPDATE_ADMIN_OPTIONS: string = 'UPDATE_ADMIN_OPTIONS'; 9 | export const UPDATE_SYNC_STATUS: string = 'UPDATE_SYNC_STATUS'; 10 | export const TOGGLE_VALUE: string = 'TOGGLE_VALUE'; 11 | export const SET_VALUE: string = 'SET_VALUE'; 12 | export const UPDATE_LOCAL_OPTION: string = 'UPDATE_LOCAL_OPTION'; 13 | export const SET_MESSAGE: string = 'SET_MESSAGE'; 14 | export const CHANGE_INSTALL_STATE: string = 'CHANGE_INSTALL_STATE'; 15 | export const CHANGE_TAB_STATE: string = 'CHANGE_TAB_STATE'; 16 | export const UPDATE_EXPORT_POST_LOG: string = 'UPDATE_EXPORT_POST_LOG'; 17 | 18 | export function updateAdminOptionsAction(data: any): IAction { 19 | return { 20 | data, 21 | type: UPDATE_ADMIN_OPTIONS, 22 | }; 23 | } 24 | 25 | export function updateLocalOptionAction(key: string, newValue: string): IAction { 26 | return { 27 | data: { 28 | [key]: newValue, 29 | }, 30 | type: UPDATE_LOCAL_OPTION, 31 | }; 32 | } 33 | 34 | export function updateSyncStatusAction(data: any): IAction { 35 | return { 36 | data, 37 | type: UPDATE_SYNC_STATUS, 38 | }; 39 | } 40 | 41 | export function toggleValueAction(key: string): IAction { 42 | return { 43 | data: key, 44 | type: TOGGLE_VALUE, 45 | }; 46 | } 47 | 48 | export function setValueAction(key: string, newValue: any): IAction { 49 | return { 50 | data: { 51 | [key]: newValue, 52 | }, 53 | type: SET_VALUE, 54 | }; 55 | } 56 | 57 | export function setMessageAction(message: IMessage): IAction { 58 | return { 59 | data: message, 60 | type: SET_MESSAGE, 61 | }; 62 | } 63 | 64 | export function changeInstallStateAction(state: InstallationState): IAction { 65 | return { 66 | data: state, 67 | type: CHANGE_INSTALL_STATE, 68 | }; 69 | } 70 | 71 | export function changeTabStateAction(tab: string): IAction { 72 | return { 73 | data: tab, 74 | type: CHANGE_TAB_STATE, 75 | }; 76 | } 77 | 78 | export function updateExportPostLogAction(log: IExportPostLog): IAction { 79 | return { 80 | data: log, 81 | type: UPDATE_EXPORT_POST_LOG, 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /frontend/src/ts/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import * as ReactRedux from 'react-redux'; 4 | import * as Redux from 'redux'; 5 | import { 6 | changeInstallStateAction, 7 | changeTabStateAction, 8 | setMessageAction, 9 | setValueAction, 10 | updateAdminOptionsAction, 11 | updateSyncStatusAction, 12 | } from './actions'; 13 | import MainContainer from './containers/MainContainer'; 14 | import { DisqusApi } from './DisqusApi'; 15 | import adminApp from './reducers/adminApp'; 16 | import { IAdminOptions } from './reducers/AdminOptions'; 17 | import AdminState, { InstallationState } from './reducers/AdminState'; 18 | import { ISyncStatus } from './reducers/SyncStatus'; 19 | import { IRestResponse, WordPressRestApi } from './WordPressRestApi'; 20 | 21 | const store = Redux.createStore(adminApp); 22 | 23 | const element: HTMLElement = document.getElementById('disqus-admin'); 24 | 25 | const onClearMessage = (event: React.SyntheticEvent): void => { 26 | store.dispatch(setMessageAction(null)); 27 | }; 28 | 29 | // Sets up the hashchange router for configuration tabs. 30 | const onHashChange = (event: HashChangeEvent): void => { 31 | if (event) 32 | event.preventDefault(); 33 | store.dispatch(changeTabStateAction(window.location.hash.replace('#', ''))); 34 | }; 35 | 36 | window.addEventListener('hashchange', onHashChange); 37 | 38 | if (window.location.hash) 39 | onHashChange(null); 40 | 41 | ReactDOM.render( 42 | 43 | 44 | , 45 | element, 46 | () => { 47 | const checkResponse = (response: IRestResponse): boolean => { 48 | if (!response) 49 | return false; 50 | 51 | if (response.code !== 'OK') { 52 | store.dispatch(setMessageAction({ 53 | onDismiss: onClearMessage, 54 | text: response.message, 55 | type: 'error', 56 | })); 57 | return false; 58 | } 59 | 60 | return true; 61 | }; 62 | 63 | store.dispatch(setValueAction('isFetchingAdminOptions', true)); 64 | store.dispatch(setValueAction('isFetchingSyncStatus', true)); 65 | 66 | // Fetch the admin options 67 | WordPressRestApi.instance.pluginRestGet('settings', (response: IRestResponse) => { 68 | store.dispatch(setValueAction('isFetchingAdminOptions', false)); 69 | 70 | if (!checkResponse(response)) 71 | return; 72 | 73 | const data: IAdminOptions = response.data; 74 | DisqusApi.instance.configure(data.disqus_public_key, data.disqus_admin_access_token, data.disqus_forum_url); 75 | 76 | store.dispatch(updateAdminOptionsAction(response.data)); 77 | 78 | if (response.data.disqus_installed) 79 | store.dispatch(changeInstallStateAction(InstallationState.installed)); 80 | }); 81 | 82 | // Fetch the sync status 83 | WordPressRestApi.instance.pluginRestGet('sync/status', (response: IRestResponse) => { 84 | store.dispatch(setValueAction('isFetchingSyncStatus', false)); 85 | 86 | if (!checkResponse(response)) 87 | return; 88 | 89 | store.dispatch(updateSyncStatusAction(response.data)); 90 | }); 91 | }, 92 | ); 93 | -------------------------------------------------------------------------------- /frontend/src/ts/components/AdvancedConfigForm.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { IFormProps } from './FormProps'; 3 | 4 | /* tslint:disable:max-line-length */ 5 | const AdvancedConfigForm = (props: IFormProps) => ( 6 |
7 | 8 | 9 | 10 | 15 | 27 | 28 | 29 |
11 | 14 | 16 | 23 |

24 | {__('This will render the Disqus comments javascript directly into the page markup, rather than use the wp_enqueue_script() function. Enable this if Disqus doesn\'t load on your posts.')} 25 |

26 |
30 |

31 | 37 |

38 |
39 | ); 40 | /* tslint:enable:max-line-length */ 41 | 42 | export default AdvancedConfigForm; 43 | -------------------------------------------------------------------------------- /frontend/src/ts/components/ExportComments.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ExportLogStaus, IExportPostLog } from '../reducers/AdminState'; 3 | import { IFormProps } from './FormProps'; 4 | 5 | const getStatusMessage = (props: IFormProps): JSX.Element => { 6 | if (props.data.isExportRunning) 7 | return

{__('Sending to Disqus') + '…'}

; 8 | 9 | return ( 10 |

11 | 12 | {__('Done!')} 13 | {' '} 14 | 15 | {__('Check your import status')} 16 | 17 | 18 |

19 | ); 20 | }; 21 | 22 | const LogMessages = (props: IFormProps) => { 23 | const logArray: IExportPostLog[] = props.data.exportLogs.toArray(); 24 | const logElements: JSX.Element[] = logArray.map((log: IExportPostLog) => { 25 | let statusColor: string; 26 | let statusText: JSX.Element|string; 27 | switch (log.status) { 28 | case ExportLogStaus.failed: 29 | statusColor = 'red'; 30 | statusText = {__('Failed')}; 31 | break; 32 | case ExportLogStaus.complete: 33 | statusColor = 'green'; 34 | statusText = __('Complete'); 35 | break; 36 | case ExportLogStaus.pending: 37 | default: 38 | statusColor = 'gray'; 39 | statusText = __('Pending'); 40 | break; 41 | } 42 | 43 | return ( 44 | 45 | {log.id} 46 | {log.title} 47 | {statusText} 48 | {log.numComments} 49 | 50 | ); 51 | }); 52 | 53 | return ( 54 |
55 | {getStatusMessage(props)} 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | {logElements} 67 | 68 |
{__('ID')}{__('Title')}{__('Status')}{__('# Imported')}
69 |
70 | ); 71 | }; 72 | 73 | const ExportComments = (props: IFormProps) => ( 74 |
79 |

80 | 85 |

86 | {props.data.exportLogs.size ? : null} 87 | 88 | ); 89 | 90 | export default ExportComments; 91 | -------------------------------------------------------------------------------- /frontend/src/ts/components/FormProps.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import AdminState, { InstallationState } from '../reducers/AdminState'; 3 | 4 | export interface IFormProps { 5 | data: AdminState; 6 | onCopyText(textValue: string): void; 7 | onDateSelectorInputchange(stateKey: string, event: React.SyntheticEvent): void; 8 | onGenerateRandomSyncToken(event: React.SyntheticEvent): void; 9 | onToggleState(stateKey: string): void; 10 | onInputChange(stateKey: string, event: React.SyntheticEvent): void; 11 | onSubmitExportCommentsForm(event: React.SyntheticEvent): void; 12 | onSubmitManualSyncForm(event: React.SyntheticEvent): void; 13 | onSubmitSiteForm(event: React.SyntheticEvent): void; 14 | onSubmitSyncConfigForm(event: React.SyntheticEvent): void; 15 | onUpdateInstallationState(newState: InstallationState): void; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/ts/components/HelpResources.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const HelpResources = () => ( 4 | 19 | ); 20 | 21 | export default HelpResources; 22 | -------------------------------------------------------------------------------- /frontend/src/ts/components/Loading.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const Loading = () => ( 4 |
5 |
6 |
7 | ); 8 | 9 | export default Loading; 10 | -------------------------------------------------------------------------------- /frontend/src/ts/components/Main.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import InstallContainer from '../containers/InstallContainer'; 3 | import SyncTokenContainer from '../containers/SyncTokenContainer'; 4 | import { getWordpressAdminUrl } from '../utils'; 5 | import Admin from './Admin'; 6 | import { IFormProps } from './FormProps'; 7 | import Loading from './Loading'; 8 | import Message from './Message'; 9 | 10 | const PRE_RELEASE_TYPES: any = Object.freeze({ 11 | alpha: 'alpha', 12 | beta: 'beta', 13 | }); 14 | 15 | /* tslint:disable:max-line-length */ 16 | const getMainView = (props: IFormProps) => { 17 | if (!props.data.config.permissions.canManageSettings) 18 | return __('You don\'t have permission to make any changes here. Please contact the site administrator to get access.'); 19 | else if (props.data.isFetchingAdminOptions || props.data.isFetchingSyncStatus) 20 | return ; 21 | else if (!props.data.adminOptions.disqus_sync_token) 22 | return ( 23 |
24 |
25 |

26 | {__('The plugin was unable to generate a secret key for your site. You will have to create one below in order for installation and syncing to work.')} 27 |

28 |
29 | 30 |
31 | ); 32 | return ; 33 | }; 34 | 35 | const getPreReleaseNotice = (pluginVersion: string) => { 36 | // Format of versions can be either 1.0.0, 1.0.0-beta, or 1.0.0-beta.1 37 | const preReleaseType = (pluginVersion.split('-')[1] || '').split('.')[0]; 38 | if (PRE_RELEASE_TYPES[preReleaseType]) { 39 | return ( 40 |
41 |

42 | You are using a pre-release version ({`${pluginVersion}`}) of the Disqus WordPress plugin. 43 | {' '}Check for new releases 44 |

45 |
46 | ); 47 | } 48 | return null; 49 | }; 50 | 51 | const Main = (props: IFormProps) => ( 52 |
53 |
54 | 55 | 59 | 60 |
61 |
62 | {getPreReleaseNotice(props.data.config.site.pluginVersion)} 63 | {props.data.message ? : null} 64 | {getMainView(props)} 65 |
66 |
67 | ); 68 | /* tslint:enable:max-line-length */ 69 | 70 | export default Main; 71 | -------------------------------------------------------------------------------- /frontend/src/ts/components/ManualSyncForm.tsx: -------------------------------------------------------------------------------- 1 | import * as moment from 'moment'; 2 | import * as React from 'react'; 3 | import { IFormProps } from './FormProps'; 4 | 5 | const ManualSyncForm = (props: IFormProps) => { 6 | return ( 7 |
12 |

{__('Manually Sync Comments')}

13 |

14 | {__('Select a time range to sync past comments. Date ranges can go up to a maximum of 5 years.')} 15 |

16 | 17 | 18 | 19 | 24 | 40 | 41 | 42 | 47 | 63 | 64 | {props.data.syncStatus.is_manual && props.data.syncStatus.progress_message ? 65 | 66 | 71 | 74 | 75 | : null} 76 | 77 |
20 | 23 | 25 | 36 |

37 | {__('The start date for the manual sync')} 38 |

39 |
43 | 46 | 48 | 59 |

60 | {__('The end date for the manual sync')} 61 |

62 |
67 | 70 | 72 | {props.data.syncStatus.progress_message} 73 |
78 | 79 | {props.data.syncStatus.is_manual && props.data.syncStatus.last_message ? 80 |

{props.data.syncStatus.last_message}

81 | : null} 82 | 83 |

84 | 89 |

90 |
91 | ); 92 | }; 93 | 94 | export default ManualSyncForm; 95 | -------------------------------------------------------------------------------- /frontend/src/ts/components/Message.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { IMessage } from '../reducers/AdminState'; 3 | 4 | const getDismissButton = (props: IMessage) => { 5 | if (!props.onDismiss) 6 | return null; 7 | 8 | return ( 9 | 12 | ); 13 | }; 14 | 15 | const Message = (props: IMessage) => ( 16 |
17 |

{props.text}

18 | {getDismissButton(props)} 19 |
20 | ); 21 | 22 | export default Message; 23 | -------------------------------------------------------------------------------- /frontend/src/ts/components/SSOConfigForm.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { IFormProps } from './FormProps'; 3 | 4 | /* tslint:disable:max-line-length */ 5 | const SSOConfigForm = (props: IFormProps) => ( 6 |
7 | 8 | 9 | 10 | 15 | 27 | 28 | 29 | 34 | 54 | 55 | 56 |
11 | 14 | 16 | 23 |

24 | {__('This will enable Single Sign-on for this site, if already enabled for your Disqus organization.')} 25 |

26 |
30 | 33 | 35 | 43 |

44 | {__('A link to a .png, .gif, or .jpg image to show as a button in Disqus.')} 45 | {' '} 46 | 50 | {__('Learn More')} 51 | 52 |

53 |
57 |

58 | 64 |

65 |
66 | ); 67 | /* tslint:enable:max-line-length */ 68 | 69 | export default SSOConfigForm; 70 | -------------------------------------------------------------------------------- /frontend/src/ts/components/SupportDiagnostics.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { IFormProps } from './FormProps'; 3 | 4 | const DIAGNOSTICS_TEXTAREA_ID = 'diagnostics-textarea'; 5 | 6 | const SupportDiagnostics = (props: IFormProps) => ( 7 |
8 |
9 |