diff --git a/.ci-env.sh b/.ci-env.sh index ce01f31e..a6c977f6 100644 --- a/.ci-env.sh +++ b/.ci-env.sh @@ -1,10 +1,17 @@ +# Use the develop branch for WPCS for compatibility with PHPCS 2.0 +export WPCS_GIT_TREE=develop + # Set up for the PHPUnit pass. setup-phpunit() { - if [[ $( php --version | grep ' 5.2' ) ]]; then - mkdir -p vendor/jdgrimes/wp-plugin-uninstall-tester \ - && git clone https://github.com/JDGrimes/wp-plugin-uninstall-tester.git vendor/jdgrimes/wp-plugin-uninstall-tester + if [[ $TRAVIS_PHP_VERSION == '5.2' ]]; then + mkdir -p vendor/jdgrimes/wp-plugin-uninstall-tester + curl -L https://github.com/JDGrimes/wp-plugin-uninstall-tester/archive/master.tar.gz \ + | tar xvz --strip-components=1 -C vendor/jdgrimes/wp-plugin-uninstall-tester + elif [[ $TRAVIS_PHP_VERSION == hhvm ]]; then + composer require satooshi/php-coveralls:dev-master + mkdir -p build/logs else composer install fi @@ -12,7 +19,14 @@ setup-phpunit() { wget -O /tmp/install-wp-tests.sh \ https://raw.githubusercontent.com/wp-cli/wp-cli/master/templates/install-wp-tests.sh + sed -i 's/$WP_VERSION == '"'"'latest'"'"'/$WP_VERSION == '"'"'stable'"'"'/' \ + /tmp/install-wp-tests.sh + bash /tmp/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION + + sed -i 's/do_action( '"'"'admin_init'"'"' )/if ( ! isset( $GLOBALS['"'"'_did_admin_init'"'"'] ) \&\& $GLOBALS['"'"'_did_admin_init'"'"'] = true ) do_action( '"'"'admin_init'"'"' )/' \ + /tmp/wordpress-tests/includes/testcase-ajax.php + cd /tmp/wordpress/wp-content/plugins ln -s $PLUGIN_DIR $PLUGIN_SLUG cd $PLUGIN_DIR @@ -33,16 +47,13 @@ setup-codesniff() { npm install -g jshint - if [ -e composer.json ]; then - wget http://getcomposer.org/composer.phar \ - && php composer.phar install --dev - fi + composer install } # Check php files for syntax errors. codesniff-php-syntax() { - if [[ $TRAVISCI_RUN == codesniff ]] || [[ $TRAVISCI_RUN == phpunit ]]; then - find . ! -path "./bin/*" ! -path "./vendor/*" \( -name '*.php' -o -name '*.inc' \) \ + if [[ $TRAVISCI_RUN == codesniff ]] || [[ $TRAVISCI_RUN == phpunit && $WP_VERSION == stable && $TRAVIS_PHP_VERSION != '5.3' ]]; then + find . ! -path "./dev-lib/*" ! -path "./vendor/*" \( -name '*.php' -o -name '*.inc' \) \ -exec php -lf {} \; else echo 'Not running PHP syntax check.' @@ -52,7 +63,7 @@ codesniff-php-syntax() { # Check php files with PHPCodeSniffer. codesniff-phpcs() { if [[ $TRAVISCI_RUN == codesniff ]]; then - $PHPCS_DIR/scripts/phpcs -n --standard=$WPCS_STANDARD \ + $PHPCS_DIR/scripts/phpcs -ns --standard=$WPCS_STANDARD \ $(if [ -n "$PHPCS_IGNORE" ]; then echo --ignore=$PHPCS_IGNORE; fi) \ $(find . -name '*.php') else @@ -81,7 +92,7 @@ codesniff-l10n() { # Check XML files for syntax errors. codesniff-xmllint() { if [[ $TRAVISCI_RUN == codesniff ]]; then - xmllint --noout $(find . ! -path "./bin/*" ! -path "./vendor/*" \( -name '*.xml' -o -name '*.xml.dist' \)) + xmllint --noout $(find . ! -path "./dev-lib/*" ! -path "./vendor/*" \( -name '*.xml' -o -name '*.xml.dist' \)) else echo 'Not running xmlint.' fi @@ -89,75 +100,76 @@ codesniff-xmllint() { # Run basic PHPUnit tests. phpunit-basic() { - if [[ $TRAVISCI_RUN == phpunit ]]; then - phpunit \ - $( - if [ -e .coveralls.yml ]; then - echo --coverage-clover build/logs/clover.xml - fi - ) - else + if [[ $TRAVISCI_RUN != phpunit ]]; then echo 'Not running PHPUnit.' + return fi -} -# Run uninstall PHPUnit tests. -phpunit-uninstall() { - if [[ $TRAVISCI_RUN == phpunit ]]; then - if [[ $( php --version | grep ' 5.2' ) ]]; then - sed -i '' -e 's/uninstall<\/group>//' ./phpunit.xml.dist + local TEST_GROUP=${1-''} + local CLOVER_FILE=${2-basic} + + local GROUP_OPTION='' + local COVERAGE_OPTION='' + + if [[ $WP_VERSION == '3.8' && $TEST_GROUP == ajax && $WP_MULTISITE == 1 ]]; then + echo 'Not running multisite Ajax tests on 3.8, see https://github.com/WordPoints/wordpoints/issues/239.' + return + fi + + if [[ $TEST_GROUP != '' ]]; then + GROUP_OPTION="--group=$TEST_GROUP" + CLOVER_FILE+="-$TEST_GROUP" + + if [[ $TRAVIS_PHP_VERSION == '5.2' ]]; then + sed -i '' -e "s/$TEST_GROUP<\/group>//" ./phpunit.xml.dist fi + fi - phpunit --group=uninstall - else - echo 'Not running PHPUnit.' + if [[ $TRAVIS_PHP_VERSION == hhvm ]]; then + COVERAGE_OPTION="--coverage-clover build/logs/clover-$CLOVER_FILE.xml" fi + + phpunit $GROUP_OPTION $COVERAGE_OPTION +} + +# Run uninstall PHPUnit tests. +phpunit-uninstall() { + phpunit-basic uninstall } # Run Ajax PHPUnit tests. phpunit-ajax() { - if - [[ $TRAVISCI_RUN == phpunit ]] \ - && [[ $WP_MULTISITE == 0 || $WP_VERSION == latest ]]; - then - if [[ $( php --version | grep ' 5.2' ) ]]; then - sed -i '' -e 's/ajax<\/group>//' ./phpunit.xml.dist - fi + phpunit-basic ajax +} - phpunit --group=ajax - else - echo 'Not running Ajax tests.' - fi +# Run the basic tests on multisite. +phpunit-ms() { + WP_MULTISITE=1 phpunit-basic '' ms +} + +# Run the uninstall tests on multisite. +phpunit-ms-uninstall() { + WP_MULTISITE=1 phpunit-basic uninstall ms +} + +# Run the ajax tests on multisite. +phpunit-ms-ajax() { + WP_MULTISITE=1 phpunit-basic ajax ms } # Run basic tests for multisite in network mode. phpunit-ms-network() { - if [[ $TRAVISCI_RUN == phpunit ]] && [[ $WP_MULTISITE == 1 ]]; then - WORDPOINTS_NETWORK_ACTIVE=1 phpunit - else - echo 'Not running network tests.' - fi + WORDPOINTS_NETWORK_ACTIVE=1 WP_MULTISITE=1 phpunit-basic '' ms-network } # Run uninstall tests in multisite in network mode. phpunit-ms-network-uninstall() { - if [[ $TRAVISCI_RUN == phpunit ]] && [[ $WP_MULTISITE == 1 ]]; then - WORDPOINTS_NETWORK_ACTIVE=1 phpunit --group=uninstall - else - echo 'Not running network tests.' - fi + WORDPOINTS_NETWORK_ACTIVE=1 WP_MULTISITE=1 phpunit-basic uninstall ms-network } # Run Ajax tests in multisite in network mode. phpunit-ms-network-ajax() { - if - [[ $TRAVISCI_RUN == phpunit ]] \ - && [[ $WP_MULTISITE == 1 ]] && [[ $WP_VERSION == latest ]]; - then - WORDPOINTS_NETWORK_ACTIVE=1 phpunit --group=ajax - else - echo 'Not running network Ajax tests.' - fi + WORDPOINTS_NETWORK_ACTIVE=1 WP_MULTISITE=1 phpunit-basic ajax ms-network } # EOF diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 00000000..46e4bf20 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,2 @@ +src_dir: src +coverage_clover: build/logs/clover-*.xml \ No newline at end of file diff --git a/.gitignore b/.gitignore index a725465a..9f33dd5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -vendor/ \ No newline at end of file +vendor/ +.idea/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..431ecf16 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "dev-lib"] + path = dev-lib + url = https://github.com/xwp/wp-dev-lib.git diff --git a/.jshintignore b/.jshintignore index 46929cdb..e20b608b 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1 +1,2 @@ -src/assets/js/jquery.datatables.min.js \ No newline at end of file +src/assets/js/jquery.datatables.min.js +vendor/ \ No newline at end of file diff --git a/.jshintrc b/.jshintrc index e5669256..3f12b8f9 120000 --- a/.jshintrc +++ b/.jshintrc @@ -1 +1 @@ -bin/.jshintrc \ No newline at end of file +dev-lib/.jshintrc \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index bf10f4ea..f07e525c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,20 +12,19 @@ php: env: - TRAVISCI_RUN=codesniff - - TRAVISCI_RUN=phpunit WP_VERSION=latest WP_MULTISITE=0 - - TRAVISCI_RUN=phpunit WP_VERSION=latest WP_MULTISITE=1 - - TRAVISCI_RUN=phpunit WP_VERSION=3.9 WP_MULTISITE=0 - - TRAVISCI_RUN=phpunit WP_VERSION=3.9 WP_MULTISITE=1 - - TRAVISCI_RUN=phpunit WP_VERSION=3.8 WP_MULTISITE=0 - - TRAVISCI_RUN=phpunit WP_VERSION=3.8 WP_MULTISITE=1 + - TRAVISCI_RUN=phpunit WP_VERSION=latest + - TRAVISCI_RUN=phpunit WP_VERSION=stable + - TRAVISCI_RUN=phpunit WP_VERSION=4.0 + - TRAVISCI_RUN=phpunit WP_VERSION=3.9 + - TRAVISCI_RUN=phpunit WP_VERSION=3.8 + +sudo: false matrix: include: # Only run HHVM against the latest for now. - php: hhvm - env: TRAVISCI_RUN=phpunit WP_VERSION=latest WP_MULTISITE=0 - - php: hhvm - env: TRAVISCI_RUN=phpunit WP_VERSION=latest WP_MULTISITE=1 + env: TRAVISCI_RUN=phpunit WP_VERSION=stable exclude: # The codesniff pass only needs to be run once, I chose PHP 5.3, since WPCS requires it. - php: 5.2 @@ -47,7 +46,7 @@ before_install: - export PHPCS_DIR=/tmp/phpcs - export PHPCS_GITHUB_SRC=squizlabs/PHP_CodeSniffer - export PHPCS_GIT_TREE=master - - export PHPCS_IGNORE='vendor/*,bin/*' + - export PHPCS_IGNORE='vendor/*,dev-lib/*' - export WPCS_DIR=/tmp/wpcs - export WPCS_GITHUB_SRC=WordPress-Coding-Standards/WordPress-Coding-Standards - export WPCS_GIT_TREE=master @@ -71,11 +70,14 @@ script: - codesniff-l10n - codesniff-xmllint - phpunit-basic + - phpunit-ms - phpunit-ms-network - phpunit-uninstall + - phpunit-ms-uninstall - phpunit-ms-network-uninstall - phpunit-ajax + - phpunit-ms-ajax - phpunit-ms-network-ajax after_script: - - if [ -e .coveralls.yml ]; then php vendor/bin/coveralls; fi + - if [[ $TRAVIS_PHP_VERSION == hhvm ]]; then php vendor/bin/coveralls; fi diff --git a/README.md b/README.md index 5b510bd9..c856c9fb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -WordPoints [![Build Status](https://travis-ci.org/WordPoints/wordpoints.png?branch=master)](https://travis-ci.org/WordPoints/wordpoints) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/WordPoints/wordpoints/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/WordPoints/wordpoints/?branch=master) [![Translation Status](https://hosted.weblate.org/widgets/wordpoints/-/shields-badge.svg)](https://hosted.weblate.org/engage/wordpoints/?utm_source=widget) +WordPoints [![Build Status](https://travis-ci.org/WordPoints/wordpoints.png?branch=master)](https://travis-ci.org/WordPoints/wordpoints) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/WordPoints/wordpoints/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/WordPoints/wordpoints/?branch=master) [![Coverage Status](https://img.shields.io/coveralls/WordPoints/wordpoints.svg)](https://coveralls.io/r/WordPoints/wordpoints?branch=master) [![Translation Status](https://hosted.weblate.org/widgets/wordpoints/-/shields-badge.svg)](https://hosted.weblate.org/engage/wordpoints/?utm_source=widget) ========== This is were development of the WordPoints WordPress plugin takes place. To diff --git a/assets/screenshot-4.png b/assets/screenshot-4.png new file mode 100644 index 00000000..e6beb462 Binary files /dev/null and b/assets/screenshot-4.png differ diff --git a/assets/screenshot-5.png b/assets/screenshot-5.png new file mode 100644 index 00000000..f10e6098 Binary files /dev/null and b/assets/screenshot-5.png differ diff --git a/bin/.coveralls.yml b/bin/.coveralls.yml deleted file mode 100644 index e7344fb7..00000000 --- a/bin/.coveralls.yml +++ /dev/null @@ -1,4 +0,0 @@ -service_name: travis-ci -src_dir: . -coverage_clover: build/logs/clover.xml -json_path: build/logs/coveralls-upload.json diff --git a/bin/.jshintrc b/bin/.jshintrc deleted file mode 100644 index bf112642..00000000 --- a/bin/.jshintrc +++ /dev/null @@ -1,20 +0,0 @@ -{ - "boss": true, - "curly": true, - "eqeqeq": true, - "eqnull": true, - "expr": true, - "immed": true, - "noarg": true, - "quotmark": "single", - "trailing": true, - "undef": true, - "unused": true, - - "browser": true, - - "globals": { - "jQuery": false, - "wp": false - } -} diff --git a/bin/.travis.yml b/bin/.travis.yml deleted file mode 100644 index 60d65d77..00000000 --- a/bin/.travis.yml +++ /dev/null @@ -1,45 +0,0 @@ -language: - - php - - node_js - -php: - - 5.3 - - 5.4 - -node_js: - - 0.10 - -env: - - WP_VERSION=latest WP_MULTISITE=0 - - WP_VERSION=latest WP_MULTISITE=1 - - WP_VERSION=3.8 WP_MULTISITE=0 - - WP_VERSION=3.8 WP_MULTISITE=1 - -before_script: - - export WP_TESTS_DIR=/tmp/wordpress-tests/ - - export PLUGIN_DIR=$(pwd) - - export PLUGIN_SLUG=$(basename $(pwd) | sed 's/^wp-//') - - export PHPCS_DIR=/tmp/phpcs - - export PHPCS_GITHUB_SRC=squizlabs/PHP_CodeSniffer - - export PHPCS_GIT_TREE=master - - export PHPCS_IGNORE='tests/*,vendor/*' - - export WPCS_DIR=/tmp/wpcs - - export WPCS_GITHUB_SRC=WordPress-Coding-Standards/WordPress-Coding-Standards - - export WPCS_GIT_TREE=master - - export WPCS_STANDARD=$(if [ -e phpcs.ruleset.xml ]; then echo phpcs.ruleset.xml; else echo WordPress-Core; fi) - - if [ -e .ci-env.sh ]; then source .ci-env.sh; fi - - if [ -e phpunit.xml ] || [ -e phpunit.xml.dist ]; then wget -O /tmp/install-wp-tests.sh https://raw.githubusercontent.com/wp-cli/wp-cli/master/templates/install-wp-tests.sh; bash /tmp/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION; cd /tmp/wordpress/wp-content/plugins; ln -s $PLUGIN_DIR $PLUGIN_SLUG; cd $PLUGIN_DIR; fi - - mkdir -p $PHPCS_DIR && curl -L https://github.com/$PHPCS_GITHUB_SRC/archive/$PHPCS_GIT_TREE.tar.gz | tar xvz --strip-components=1 -C $PHPCS_DIR - - mkdir -p $WPCS_DIR && curl -L https://github.com/$WPCS_GITHUB_SRC/archive/$WPCS_GIT_TREE.tar.gz | tar xvz --strip-components=1 -C $WPCS_DIR - - $PHPCS_DIR/scripts/phpcs --config-set installed_paths $WPCS_DIR - - npm install -g jshint - - if [ -e composer.json ]; then wget http://getcomposer.org/composer.phar && php composer.phar install --dev; fi - -script: - - find . -path ./bin -prune -o \( -name '*.php' -o -name '*.inc' \) -exec php -lf {} \; - - if [ -e phpunit.xml ] || [ -e phpunit.xml.dist ]; then phpunit $( if [ -e .coveralls.yml ]; then echo --coverage-clover build/logs/clover.xml; fi ); fi - - $PHPCS_DIR/scripts/phpcs --standard=$WPCS_STANDARD $(if [ -n "$PHPCS_IGNORE" ]; then echo --ignore=$PHPCS_IGNORE; fi) $(find . -name '*.php') - - jshint . - -after_script: - - if [ -e .coveralls.yml ]; then php vendor/bin/coveralls; fi diff --git a/bin/class-wordpress-readme-parser.php b/bin/class-wordpress-readme-parser.php deleted file mode 100644 index 3314a5e8..00000000 --- a/bin/class-wordpress-readme-parser.php +++ /dev/null @@ -1,206 +0,0 @@ - (@westonruter) - * @copyright Copyright (c) 2013, X-Team - * @license GPLv2+ - */ - -class WordPress_Readme_Parser { - public $path; - public $source; - public $title = ''; - public $short_description = ''; - public $metadata = array(); - public $sections = array(); - - function __construct( $args = array() ) { - $args = array_merge( get_object_vars( $this ), $args ); - foreach ( $args as $key => $value ) { - $this->$key = $value; - } - - $this->source = file_get_contents( $this->path ); - if ( ! $this->source ) { - throw new Exception( 'readme.txt was empty or unreadable' ); - } - - // Parse metadata - $syntax_ok = preg_match( '/^=== (.+?) ===\n(.+?)\n\n(.+?)\n(.+)/s', $this->source, $matches ); - if ( ! $syntax_ok ) { - throw new Exception( 'Malformed metadata block' ); - } - $this->title = $matches[1]; - $this->short_description = $matches[3]; - $readme_txt_rest = $matches[4]; - $this->metadata = array_fill_keys( array( 'Contributors', 'Tags', 'Requires at least', 'Tested up to', 'Stable tag', 'License', 'License URI' ), null ); - foreach ( explode( "\n", $matches[2] ) as $metadatum ) { - if ( ! preg_match( '/^(.+?):\s+(.+)$/', $metadatum, $metadataum_matches ) ) { - throw new Exception( "Parse error in $metadatum" ); - } - list( $name, $value ) = array_slice( $metadataum_matches, 1, 2 ); - $this->metadata[ $name ] = $value; - } - $this->metadata['Contributors'] = preg_split( '/\s*,\s*/', $this->metadata['Contributors'] ); - $this->metadata['Tags'] = preg_split( '/\s*,\s*/', $this->metadata['Tags'] ); - - $syntax_ok = preg_match_all( '/(?:^|\n)== (.+?) ==\n(.+?)(?=\n== |$)/s', $readme_txt_rest, $section_matches, PREG_SET_ORDER ); - if ( ! $syntax_ok ) { - throw new Exception( 'Failed to parse sections from readme.txt' ); - } - foreach ( $section_matches as $section_match ) { - array_shift( $section_match ); - - $heading = array_shift( $section_match ); - $body = trim( array_shift( $section_match ) ); - $subsections = array(); - - // @todo Parse out front matter /(.+?)(\n=\s+.+$)/s - - // Parse subsections - if ( preg_match_all( '/(?:^|\n)= (.+?) =\n(.+?)(?=\n= |$)/s', $body, $subsection_matches, PREG_SET_ORDER ) ) { - $body = null; - foreach ( $subsection_matches as $subsection_match ) { - array_shift( $subsection_match ); - $subsections[] = array( - 'heading' => array_shift( $subsection_match ), - 'body' => trim( array_shift( $subsection_match ) ), - ); - } - } - - $this->sections[] = compact( 'heading', 'body', 'subsections' ); - } - } - - /** - * Convert the parsed readme.txt into Markdown - * @param array|string [$params] - * @return string - */ - function to_markdown( $params = array() ) { - - $general_section_formatter = function ( $body ) use ( $params ) { - $body = preg_replace( - '#\[youtube\s+(?:http://www\.youtube\.com/watch\?v=|http://youtu\.be/)(.+?)\]#', - '[![Play video on YouTube](http://i1.ytimg.com/vi/$1/hqdefault.jpg)](http://www.youtube.com/watch?v=$1)', - $body - ); - return $body; - }; - - // Parse sections - $section_formatters = array( - 'Description' => function ( $body ) use ( $params ) { - if ( isset( $params['travis_ci_url'] ) ) { - $body .= sprintf( "\n\n[![Build Status](%s.png?branch=master)](%s)", $params['travis_ci_url'], $params['travis_ci_url'] ); - } - if ( isset( $params['coveralls_url'] ) ) { - $body .= sprintf( "\n\n[![Build Status](%s?branch=master)](%s)", $params['coveralls_badge_src'], $params['coveralls_url'] ); - } - return $body; - }, - 'Screenshots' => function ( $body ) { - $body = trim( $body ); - $new_body = ''; - if ( ! preg_match_all( '/^\d+\. (.+?)$/m', $body, $screenshot_matches, PREG_SET_ORDER ) ) { - throw new Exception( 'Malformed screenshot section' ); - } - foreach ( $screenshot_matches as $i => $screenshot_match ) { - $img_extensions = array( 'jpg', 'gif', 'png' ); - foreach ( $img_extensions as $ext ) { - $filepath = sprintf( 'assets/screenshot-%d.%s', $i + 1, $ext ); - if ( file_exists( dirname( $this->path ) . DIRECTORY_SEPARATOR . $filepath ) ) { - break; - } - else { - $filepath = null; - } - } - if ( empty( $filepath ) ) { - continue; - } - - $screenshot_name = $screenshot_match[1]; - $new_body .= sprintf( "### %s\n", $screenshot_name ); - $new_body .= "\n"; - $new_body .= sprintf( "![%s](%s)\n", $screenshot_name, $filepath ); - $new_body .= "\n"; - } - return $new_body; - }, - ); - - // Format metadata - $formatted_metadata = $this->metadata; - $formatted_metadata['Contributors'] = join( - ', ', - array_map( - function ( $contributor ) { - $contributor = strtolower( $contributor ); - // @todo Map to GitHub account - return sprintf( '[%1$s](http://profiles.wordpress.org/%1$s)', $contributor ); - }, - $this->metadata['Contributors'] - ) - ); - $formatted_metadata['Tags'] = join( - ', ', - array_map( - function ( $tag ) { - return sprintf( '[%1$s](http://wordpress.org/plugins/tags/%1$s)', $tag ); - }, - $this->metadata['Tags'] - ) - ); - $formatted_metadata['License'] = sprintf( '[%s](%s)', $formatted_metadata['License'], $formatted_metadata['License URI'] ); - unset( $formatted_metadata['License URI'] ); - if ( $this->metadata['Stable tag'] === 'trunk' ) { - $formatted_metadata['Stable tag'] .= ' (master)'; - } - - // Render metadata - $markdown = "\n"; - $markdown .= sprintf( "# %s\n", $this->title ); - $markdown .= "\n"; - if ( file_exists( 'assets/banner-1544x500.png' ) ) { - $markdown .= '![Banner](assets/banner-1544x500.png)'; - $markdown .= "\n"; - } - $markdown .= sprintf( "%s\n", $this->short_description ); - $markdown .= "\n"; - foreach ( $formatted_metadata as $name => $value ) { - $markdown .= sprintf( "**%s:** %s \n", $name, $value ); - } - $markdown .= "\n"; - - foreach ( $this->sections as $section ) { - $markdown .= sprintf( "## %s ##\n", $section['heading'] ); - $markdown .= "\n"; - - $body = $section['body']; - - $body = call_user_func( $general_section_formatter, $body ); - if ( isset( $section_formatters[ $section['heading'] ] ) ) { - $body = trim( call_user_func( $section_formatters[ $section['heading'] ], $body ) ); - } - - if ( $body ) { - $markdown .= sprintf( "%s\n", $body ); - } - foreach ( $section['subsections'] as $subsection ) { - $markdown .= sprintf( "### %s ###\n", $subsection['heading'] ); - $markdown .= sprintf( "%s\n", $subsection['body'] ); - $markdown .= "\n"; - } - - $markdown .= "\n"; - } - - return $markdown; - } - -} diff --git a/bin/contributing.md b/bin/contributing.md deleted file mode 100644 index 8d23e4bd..00000000 --- a/bin/contributing.md +++ /dev/null @@ -1,3 +0,0 @@ -Pull requests should be opened with the `develop` branch as the base. Do not -open pull requests into the `master` branch, as this branch contains the latest -stable release. diff --git a/bin/generate-markdown-readme b/bin/generate-markdown-readme deleted file mode 100755 index 9f4272cd..00000000 --- a/bin/generate-markdown-readme +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env php - (@westonruter) - * @copyright Copyright (c) 2013, X-Team - * @license GPLv2+ - */ - -try { - if ( php_sapi_name() !== 'cli' ) { - throw new Exception( 'Only allowed in CLI mode.' ); - } - - $readme_txt_path = null; - while ( true ) { - foreach ( array( 'readme.txt', 'README.txt' ) as $readme_filename ) { - if ( file_exists( $readme_filename ) ) { - $readme_txt_path = realpath( $readme_filename ); - break; - } - } - - $old_cwd = getcwd(); - if ( ! empty( $readme_txt_path ) || ! chdir( '..' ) || getcwd() === $old_cwd ) { - break; - } - } - if ( empty( $readme_txt_path ) ) { - throw new Exception( 'Failed to find a readme.txt or README.txt above the current working directory.' ); - } - - $readme_root = dirname( $readme_txt_path ); - $readme_md_path = preg_replace( '/txt$/', 'md', $readme_txt_path ); - - require_once __DIR__ . '/class-wordpress-readme-parser.php'; - - $readme = new WordPress_Readme_Parser( array( 'path' => $readme_txt_path ) ); - - $md_args = array(); - $github_url_regex = '#.+github\.com[:/]([^/]+/[^/]+)\.git$#'; - $origin_url = trim( `git config --get remote.origin.url` ); - if ( file_exists( $readme_root . '/.travis.yml' ) ) { - $md_args['travis_ci_url'] = preg_replace( $github_url_regex, 'https://travis-ci.org/$1', $origin_url ); - } - if ( file_exists( $readme_root . '/.coveralls.yml' ) ) { - $md_args['coveralls_badge_src'] = preg_replace( $github_url_regex, 'https://coveralls.io/repos/$1/badge.png', $origin_url ); - $md_args['coveralls_url'] = preg_replace( $github_url_regex, 'https://coveralls.io/r/$1', $origin_url ); - } - $markdown = $readme->to_markdown( $md_args ); - - $is_written = file_put_contents( $readme_md_path, $markdown ); - if ( ! $is_written ) { - throw new Exception( sprintf( 'Failed to write to %s', $readme_md_path ) ); - } - fwrite( STDERR, 'Successfully converted WordPress README to Markdown' . PHP_EOL ); - fwrite( STDOUT, $readme_md_path . PHP_EOL ); -} -catch( Exception $e ) { - fwrite( STDERR, $e->getMessage() . PHP_EOL ); - exit( 1 ); -} diff --git a/bin/phpcs.ruleset.xml b/bin/phpcs.ruleset.xml deleted file mode 100644 index 60365e3e..00000000 --- a/bin/phpcs.ruleset.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - Generally-applicable sniffs for WordPress plugins - - - - /tests/*,/vendor/* - - diff --git a/bin/pre-commit b/bin/pre-commit deleted file mode 100755 index 12f0fc84..00000000 --- a/bin/pre-commit +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/bash -# WordPress Plugin pre-commit hook - -set -e - -WPCS_STANDARD=$(if [ -e phpcs.ruleset.xml ]; then echo phpcs.ruleset.xml; else echo WordPress-Core; fi) -if [ -e .ci-env.sh ]; then - source .ci-env.sh -fi - -message="Checking staged changes..." -git_status_egrep='^[MARC].+' - -for i; do - case "$i" - in - -m) - message="Checking any uncommitted changes..." - git_status_egrep='^.?[MARC].+' - shift;; - esac -done - -echo $message - -# Check for staged JS files -IFS=$'\n' staged_js_files=( $(git status --porcelain | sed 's/[^ ]* -> *//g' | egrep $git_status_egrep'\.js$' | cut -c4-) ) -if [ ${#staged_js_files[@]} != 0 ]; then - # JSHint - if [ -e .jshintrc ]; then - echo "## jslint" - if command -v jshint >/dev/null 2>&1; then - jshint "${staged_js_files[@]}" - else - echo "Skipping jshint since not installed" - fi - fi - -fi - -# Check for staged PHP files -IFS=$'\n' staged_php_files=( $(git status --porcelain | sed 's/[^ ]* -> *//g' | egrep $git_status_egrep'\.php$' | cut -c4-) ) -if [ ${#staged_php_files[@]} != 0 ]; then - # PHP Syntax Check - for php_file in "${staged_php_files[@]}"; do - php -lf $php_file - done - - # PHPUnit - if [ -e phpunit.xml ] || [ -e phpunit.xml.dist ]; then - echo "## phpunit" - if [ "$USER" != 'vagrant' ]; then - - # Check if we're in VVV - plugin_dir=$(pwd) - while [ ! -e Vagrantfile ]; do - if [ $(pwd) == '/' ] || [ "$last_dir" == $(pwd) ]; then - break - fi - last_dir=$(pwd) - cd .. - done - if [ -e Vagrantfile ] && grep -q vvv Vagrantfile; then - vvv_root=$(pwd) - absolute_vvv_plugin_path=/srv${plugin_dir:${#vvv_root}} - fi - cd $plugin_dir - - if [ ! -z "$absolute_vvv_plugin_path" ]; then - echo "Running phpunit in VVV" - vagrant ssh -c "cd $absolute_vvv_plugin_path && phpunit" - elif command -v vassh >/dev/null 2>&1; then - echo "Running phpunit in vagrant via vassh..." - vassh phpunit - fi - elif ! command -v phpunit >/dev/null 2>&1;then - echo "Skipping phpunit since not installed" - elif [ -z "$WP_TESTS_DIR" ]; then - echo "Skipping phpunit since WP_TESTS_DIR env missing" - else - phpunit - fi - fi - - # PHP_CodeSniffer WordPress Coding Standards - echo "## phpcs" - if command -v phpcs >/dev/null 2>&1; then - phpcs -p -s -v --standard=$WPCS_STANDARD $(if [ -n "$PHPCS_IGNORE" ]; then echo --ignore=$PHPCS_IGNORE; fi) "${staged_php_files[@]}" - else - echo "Skipping phpcs since not installed" - fi -fi - -# Make sure the readme.md never gets out of sync with the readme.txt -#generate_markdown_readme=$(find . -name generate-markdown-readme -print -quit) -#if [ -n "$generate_markdown_readme" ]; then -# markdown_readme_path=$($generate_markdown_readme) -# git add $markdown_readme_path -#fi diff --git a/bin/readme.md b/bin/readme.md deleted file mode 100644 index 3ace392a..00000000 --- a/bin/readme.md +++ /dev/null @@ -1,65 +0,0 @@ -wp-plugin-dev-lib -================= - -**Common code used during development of WordPress plugins** - -It is intended that this repo be included in plugin repo via git-subtree/submodule in a `bin/` directory. - -To **add** it to your repo, do: - -```bash -git subtree add --prefix bin \ - git@github.com:x-team/wp-plugin-dev-lib.git master --squash -``` - -To **update** the library with the latest changes: - -```bash -git subtree pull --prefix bin \ - git@github.com:x-team/wp-plugin-dev-lib.git master --squash -``` - -And if you have changes you want to **upstream**, do: - -```bash -git subtree push --prefix bin \ - git@github.com:x-team/wp-plugin-dev-lib.git master -``` - -Symlink to the `.travis.yml`, `.jshintrc`, and `phpcs.ruleset.xml` inside of the `bin/` directory you added: - -```bash -ln -s bin/.travis.yml . && git add .travis.yml -ln -s bin/.jshintrc . && git add .jshintrc -ln -s bin/phpcs.ruleset.xml . && git add phpcs.ruleset.xml -``` - -Symlink to `pre-commit` from your project's `.git/hooks/pre-commit`: - -```bash -cd .git/hooks -ln -s ../../bin/pre-commit . -``` - -You may customize the behavior of the `.travis.yml` and `pre-commit` hook by -specifying a `.ci-env.sh` in the root of the repo, for example: - -```bash -export PHPCS_GITHUB_SRC=x-team/PHP_CodeSniffer -export PHPCS_GIT_TREE=master -export WPCS_GIT_TREE=develop -export WPCS_STANDARD=WordPress-Core -export PHPCS_IGNORE='tests/*,includes/vendor/*' -``` - -The library includes a WordPress README [parser](class-wordpress-readme-parser.php) and [converter](generate-markdown-readme) to Markdown, -so you don't have to manually keep your `readme.txt` on WordPress.org in sync with the `readme.md` you have on GitHub. The -converter will also automatically recognize the presence of projects with Travis CI and include the status image -in the markdown. Screenshots and banner images for WordPress.org are also automatically incorporated into the `readme.md`. - -What is also included in this repo is an [`svn-push`](svn-push) to push commits from a GitHub repo to the WordPress.org SVN repo for the plugin. -The `/assets/` directory in the root of the project will get automatically moved one directory above in the SVN repo (alongside -`trunk`, `branches`, and `tags`). To use, include an `svn-url` file in the root of your repo and let this file contains he full root URL -to the WordPress.org repo for plugin (don't include `trunk`). - -The utilities in this project were first developed to facilitate development of [X-Team](http://x-team.com/wordpress/)'s [plugins](http://profiles.wordpress.org/x-team/). diff --git a/bin/svn-push b/bin/svn-push deleted file mode 100755 index c573756a..00000000 --- a/bin/svn-push +++ /dev/null @@ -1,157 +0,0 @@ -#!/bin/bash -# Given a svn-url file one directory up, export the latest git commits to the specified SVN repo. -# Create a git release tag from the version specified in the plugin file. -# Author: Weston Ruter (@westonruter) - -set -e - -cd $(dirname $0)/.. - -if [ ! -e svn-url ]; then - echo "Error: Missing svn-url file" >&2 - exit 1 -fi - -force= -while getopts 'f' option; do - case $option in - f) - force=1 - ;; - esac -done - -if [ -n "$(git status -s -uno)" ] && [ -z "$force" ]; then - git status - echo "Error: Git state has modified or staged files. Commit or reset, or supply -f" >&2 - exit 1 -fi - -git_root=$(pwd) - -current_branch=$(git rev-parse --abbrev-ref HEAD) -if [ $current_branch != 'master' ]; then - git checkout master -fi - -bin/generate-markdown-readme -git add readme.md -if [ -n "$(git status --porcelain readme.md)" ]; then - echo "Please commit (--amend?) the updated readme.md" - exit 1 -fi - -git pull origin master -git push origin master -svn_url=$(cat svn-url) -svn_repo_dir=/tmp/svn-$(basename $git_root)-$(md5sum <<< $git_root | cut -c1-32) - -for php in *.php; do - if grep -q 'Plugin Name:' $php && grep -q 'Version:' $php; then - plugin_version=$(cat $php | grep 'Version:' | sed 's/.*Version: *//') - fi -done - -if [ -z "$plugin_version" ]; then - echo "Unable to find plugin version" - exit 1 -fi - -if ! grep -q "$plugin_version" readme.txt; then - echo "Please update readme.txt to include $plugin_version in changelog" - exit 1 -fi - -if git show-ref --tags --quiet --verify -- "refs/tags/$plugin_version"; then - has_tag=1 -fi - -if [ -n "$has_tag" ] && [ -z "$force" ]; then - echo "Plugin version $plugin_version already tagged. Please bump version and try again, or supply -f" - exit 1 -fi - -if [ -z "$has_tag" ]; then - echo "Tagging plugin version $plugin_version" - git tag "$plugin_version" master - git push origin "$plugin_version" -else - echo "Skipping plugin tag $plugin_version since already exists" -fi - -if [ -e $svn_repo_dir ] && [ ! -e $svn_repo_dir/.svn ]; then - rm -rf $svn_repo_dir -fi -if [ ! -e $svn_repo_dir ]; then - svn checkout $svn_url $svn_repo_dir - cd $svn_repo_dir -else - cd $svn_repo_dir - svn up -fi - -cd $git_root - -# rsync all cached files and their directories -cat <( - git ls-files --cached --full-name $git_root \ - & - git ls-files --cached --full-name $git_root | xargs -I {} dirname {} | sort | uniq -) | sort | rsync -avz --delete --delete-excluded --include-from=- --exclude='*' ./ $svn_repo_dir/trunk/ - -cd $svn_repo_dir/trunk - -# move assets directory to proper location in SVN -if [ -d assets ]; then - rsync -avz --delete ./assets/ ../assets/ - rm -r ./assets/ -fi - -# convert .gitignores to svn:ignore -for gitignore in $(find . -name .gitignore); do - echo "Convert $gitignore to svn:global-ignores" - svn propset svn:global-ignores -F $gitignore $(dirname $gitignore) - svn rm --force $gitignore -done - -cd $svn_repo_dir - -# Delete any files from SVN that are no longer there -svn status . | grep "^\!" | sed 's/^\! *//g' | xargs svn rm - -# Add everything left to commit -if [ -d assets ]; then - svn add --force assets -fi -svn add --force trunk - -# Do SVN commit -svn_commit_file=$svn_repo_dir/COMMIT_MSG -last_pushed_commit=$(svn log -l 1 | grep -E -o '^commit ([0-9a-f]{5,})' | head -n 1 | cut -c8-) - -cd $git_root - -git log -1 --format="Update to commit %h from $(git config --get remote.origin.url)" > $svn_commit_file -echo >> $svn_commit_file -echo 'Includes the following commit(s):' >> $svn_commit_file -echo >> $svn_commit_file - -echo -n 'Obtaining last commit pushed to SVN...' -git_log_args='--pretty=short --name-status --color=never' -if [ -z "$last_pushed_commit" ]; then - echo "none; starting from beginning" - git log $git_log_args >> $svn_commit_file -else - echo "$last_pushed_commit" - git log $git_log_args $last_pushed_commit..HEAD >> $svn_commit_file -fi - -cd $svn_repo_dir - -svn commit -F $svn_commit_file -rm $svn_commit_file - -# Restore branch -if [ $current_branch != 'master' ]; then - git checkout $current_branch -fi diff --git a/composer.json b/composer.json index 72bfa443..680a9555 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,10 @@ { "require": { - "php": ">=5.2.0", + "php": ">=5.2.0" + }, + "require-dev": { "jdgrimes/wp-plugin-uninstall-tester": "~0.4", - "jdgrimes/wp-l10n-validator": "dev-master" + "jdgrimes/wp-l10n-validator": "dev-master", + "wordpoints/l10n-validator-config": "~1.0" } } \ No newline at end of file diff --git a/composer.lock b/composer.lock index 3ccc3267..0b6d6126 100644 --- a/composer.lock +++ b/composer.lock @@ -3,20 +3,21 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "64baa6a9c15e34f7f7bdc941d1260dc1", - "packages": [ + "hash": "2b18cb53426361d7e85eedd5f7779053", + "packages": [], + "packages-dev": [ { "name": "jdgrimes/wp-l10n-validator", "version": "dev-master", "source": { "type": "git", "url": "https://github.com/JDGrimes/wp-l10n-validator.git", - "reference": "bb2080e297430426fa5cc61789149dd2e4fb68dc" + "reference": "28e32f54fdaf155268d48a83b26bb2c966cbdff7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JDGrimes/wp-l10n-validator/zipball/bb2080e297430426fa5cc61789149dd2e4fb68dc", - "reference": "bb2080e297430426fa5cc61789149dd2e4fb68dc", + "url": "https://api.github.com/repos/JDGrimes/wp-l10n-validator/zipball/28e32f54fdaf155268d48a83b26bb2c966cbdff7", + "reference": "28e32f54fdaf155268d48a83b26bb2c966cbdff7", "shasum": "" }, "require": { @@ -37,7 +38,7 @@ ], "description": "Gettext localization validator for WordPress", "homepage": "https://github.com/JDGrimes/wp-l10n-validator", - "time": "2014-11-13 14:37:08" + "time": "2015-01-07 20:43:54" }, { "name": "jdgrimes/wp-plugin-uninstall-tester", @@ -72,14 +73,31 @@ "description": "Utilities for testing WordPress plugin install/uninstall with PHPUnit", "homepage": "https://github.com/JDGrimes/wp-plugin-uninstall-tester", "time": "2014-11-25 14:47:10" + }, + { + "name": "wordpoints/l10n-validator-config", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/WordPoints/l10n-validator-config.git", + "reference": "b5083fb51c17e3626b8f0279ff920d38f587971b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPoints/l10n-validator-config/zipball/b5083fb51c17e3626b8f0279ff920d38f587971b", + "reference": "b5083fb51c17e3626b8f0279ff920d38f587971b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "description": "WordPoints-specific configuration for https://github.com/JDGrimes/wp-l10n-validator", + "time": "2015-01-07 22:32:10" } ], - "packages-dev": [ - - ], - "aliases": [ - - ], + "aliases": [], "minimum-stability": "stable", "stability-flags": { "jdgrimes/wp-l10n-validator": 20 @@ -87,7 +105,5 @@ "platform": { "php": ">=5.2.0" }, - "platform-dev": [ - - ] + "platform-dev": [] } diff --git a/phpcs.ruleset.xml b/phpcs.ruleset.xml index debfc322..8b1fac48 100644 --- a/phpcs.ruleset.xml +++ b/phpcs.ruleset.xml @@ -4,18 +4,18 @@ + + - - - - - + + + /tests/* @@ -23,6 +23,21 @@ /tests/* + + /tests/* + + + /tests/* + + + + + + /tests/* + + + /tools/* + @@ -30,12 +45,6 @@ - - - - - - - /vendor/* + /vendor/*,/dev-lib/* diff --git a/src/admin/admin.php b/src/admin/admin.php index 3abd340b..140a5f3b 100644 --- a/src/admin/admin.php +++ b/src/admin/admin.php @@ -421,6 +421,69 @@ function wordpoints_upload_module_zip() { } add_action( 'update-custom_upload-wordpoints-module', 'wordpoints_upload_module_zip' ); +/** + * Notify the user when they try to install a module on the plugins screen. + * + * The function is hooked to the upgrader_source_selection action twice. The first + * time it is called, we just save a local copy of the source path. This is + * necessary because the second time around the source will be a WP_Error if there + * are no plugins in it, but we have to have the source location so that we can check + * if it is a module instead of a plugin. + * + * @since 1.9.0 + */ +function wordpoints_plugin_upload_error_filter( $source ) { + + static $_source; + + if ( ! isset( $_source ) ) { + + $_source = $source; + + } else { + + global $wp_filesystem; + + if ( + ! is_wp_error( $_source ) + && is_wp_error( $source ) + && 'incompatible_archive_no_plugins' === $source->get_error_code() + ) { + + $working_directory = str_replace( + $wp_filesystem->wp_content_dir() + , trailingslashit( WP_CONTENT_DIR ) + , $_source + ); + + if ( is_dir( $working_directory ) ) { + + // Check if the folder contains a module. + foreach ( glob( $working_directory . '*.php' ) as $file ) { + + $info = wordpoints_get_module_data( $file, false, false ); + + if ( ! empty( $info['name'] ) ) { + $source = new WP_Error( + 'wordpoints_module_archive_not_plugin' + , $source->get_error_message() + , __( 'This appears to be a WordPoints module archive. Try installing it on the WordPoints module install screen instead.', 'wordpoints' ) + ); + + break; + } + } + } + } + + unset( $_source ); + } + + return $source; +} +add_action( 'upgrader_source_selection', 'wordpoints_plugin_upload_error_filter', 5 ); +add_action( 'upgrader_source_selection', 'wordpoints_plugin_upload_error_filter', 20 ); + /** * Add a sidebar to the general settings page. * @@ -457,10 +520,14 @@ function wordpoints_admin_settings_screen_sidebar() { function wordpoints_admin_notices() { // Check if any notices have been dismissed. - if ( - isset( $_POST['wordpoints_notice'], $_POST['_wpnonce'] ) - && wp_verify_nonce( $_POST['_wpnonce'], "wordpoints_dismiss_notice-{$_POST['wordpoints_notice']}" ) - ) { + $is_notice_dismissed = wordpoints_verify_nonce( + '_wpnonce' + , 'wordpoints_dismiss_notice-%s' + , array( 'wordpoints_notice' ) + , 'post' + ); + + if ( $is_notice_dismissed && isset( $_POST['wordpoints_notice'] ) ) { wordpoints_delete_network_option( sanitize_key( $_POST['wordpoints_notice'] ) ); } diff --git a/src/admin/includes/class-wordpoints-modules-list-table.php b/src/admin/includes/class-wordpoints-modules-list-table.php index 352d08f4..a3132277 100644 --- a/src/admin/includes/class-wordpoints-modules-list-table.php +++ b/src/admin/includes/class-wordpoints-modules-list-table.php @@ -14,6 +14,26 @@ */ final class WordPoints_Modules_List_Table extends WP_List_Table { + /** + * The current instance of the class. + * + * @since 1.9.0 + * + * @var WordPoints_Modules_List_Table + */ + protected static $instance; + + /** + * Get the current instance of the list table. + * + * @since 1.9.0 + * + * @return WordPoints_Modules_List_Table The current instance. + */ + public static function instance() { + return self::$instance; + } + /** * Construct the class. * @@ -32,6 +52,8 @@ public function __construct( $args = array() ) { ) ); + self::$instance = $this; + $status = 'all'; $module_statuses = array( 'active', 'inactive', 'recently_activated', 'search' ); @@ -532,7 +554,7 @@ public function single_row( $item ) { ?> > - row_actions( $this->get_module_row_actions( $module_file, $module_data, $is_active ), true ); ?> + row_actions( $this->get_module_row_actions( $module_file, $module_data, $is_active ), true ); // XSS OK WPCS ?> > -

+
+

+ + + +   + +

+
@@ -590,7 +620,7 @@ public function single_row( $item ) { default: ?> - > + > __( 'General Settings', 'wordpoints' ), - 'components' => __( 'Components', 'wordpoints' ), - ) - ); - - switch ( wordpoints_admin_get_current_tab() ) { - - case 'components': - $template = '/configure-components.php'; - break; - - default: - $template = '/configure-settings.php'; - } - - include WORDPOINTS_DIR . 'admin/screens' . $template; - - /** - * At the bottom of the configure screens. - * - * @since 1.0.0 - */ - do_action( 'wordpoints_admin_configure_foot' ); + /** + * At the top of the configure screens. + * + * @since 1.0.0 + */ + do_action( 'wordpoints_admin_configure_head' ); + + wordpoints_admin_show_tabs( + array( + 'general' => __( 'General Settings', 'wordpoints' ), + 'components' => __( 'Components', 'wordpoints' ), + ) + ); + + switch ( wordpoints_admin_get_current_tab() ) { + + case 'components': + $template = '/configure-components.php'; + break; + + default: + $template = '/configure-settings.php'; + } + + include WORDPOINTS_DIR . 'admin/screens' . $template; + + /** + * At the bottom of the configure screens. + * + * @since 1.0.0 + */ + do_action( 'wordpoints_admin_configure_foot' ); + ?> diff --git a/src/admin/screens/modules-load.php b/src/admin/screens/modules-load.php index d2838cda..c912a44b 100644 --- a/src/admin/screens/modules-load.php +++ b/src/admin/screens/modules-load.php @@ -11,20 +11,20 @@ if ( isset( $_POST['clear-recent-list'] ) ) { $action = 'clear-recent-list'; -} elseif ( isset( $_REQUEST['action'] ) && '-1' !== $_REQUEST['action'] ) { +} elseif ( isset( $_REQUEST['action'] ) && -1 !== (int) $_REQUEST['action'] ) { $action = sanitize_key( $_REQUEST['action'] ); -} elseif ( isset( $_REQUEST['action2'] ) && '-1' !== $_REQUEST['action2'] ) { +} elseif ( isset( $_REQUEST['action2'] ) && -1 !== (int) $_REQUEST['action2'] ) { $action = sanitize_key( $_REQUEST['action2'] ); } else { $action = ''; } $page = ( isset( $_REQUEST['paged'] ) ) ? max( 1, absint( $_REQUEST['paged'] ) ) : 1; -$module = ( isset( $_REQUEST['module'] ) ) ? wp_unslash( $_REQUEST['module'] ) : ''; +$module = ( isset( $_REQUEST['module'] ) ) ? wp_unslash( sanitize_text_field( $_REQUEST['module'] ) ) : ''; $s = ( isset( $_REQUEST['s'] ) ) ? urlencode( $_REQUEST['s'] ) : ''; // Clean up request URI from temporary args for screen options/paging URI's to work as expected. -$_SERVER['REQUEST_URI'] = remove_query_arg( array( 'error', 'deleted', 'activate', 'activate-multi', 'deactivate', 'deactivate-multi', '_error_nonce' ), $_SERVER['REQUEST_URI'] ); +$_SERVER['REQUEST_URI'] = remove_query_arg( array( 'error', 'deleted', 'activate', 'activate-multi', 'deactivate', 'deactivate-multi', '_error_nonce' ) ); $redirect_url = self_admin_url( "admin.php?page=wordpoints_modules&module_status={$status}&paged={$page}&s={$s}" ); @@ -90,7 +90,9 @@ check_admin_referer( 'bulk-modules' ); - $modules = isset( $_POST['checked'] ) ? (array) wp_unslash( $_POST['checked'] ) : array(); + $modules = isset( $_POST['checked'] ) + ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['checked'] ) ) // XSS OK WPCS. + : array(); // Only activate modules which are not already active. if ( is_network_admin() ) { @@ -216,7 +218,9 @@ function wordpoints_module_sandbox_scrape( $module ) { check_admin_referer( 'bulk-modules' ); - $modules = isset( $_POST['checked'] ) ? (array) wp_unslash( $_POST['checked'] ) : array(); + $modules = isset( $_POST['checked'] ) + ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['checked'] ) ) // XSS OK WPCS. + : array(); $network_modules = array_filter( $modules, 'is_wordpoints_module_active_for_network' ); @@ -257,7 +261,9 @@ function wordpoints_module_sandbox_scrape( $module ) { check_admin_referer( 'bulk-modules' ); // $_POST = from the module form; $_GET = from the FTP details screen. - $modules = isset( $_REQUEST['checked'] ) ? (array) wp_unslash( $_REQUEST['checked'] ) : array(); + $modules = isset( $_REQUEST['checked'] ) + ? array_map( 'sanitize_text_field', (array) wp_unslash( $_REQUEST['checked'] ) ) // XSS OK WPCS. + : array(); if ( empty( $modules ) ) { wp_redirect( $redirect_url ); @@ -273,7 +279,7 @@ function wordpoints_module_sandbox_scrape( $module ) { } if ( empty( $modules ) ) { - wp_redirect( add_query_arg( array( 'error' => 'true', 'main' => 'true' ), $redirect_url ) ); + wp_redirect( add_query_arg( array( 'error' => 'true', 'main' => 'true' ), $redirect_url ) ); exit; } @@ -291,100 +297,106 @@ function wordpoints_module_sandbox_scrape( $module ) {
$data ) { + foreach ( $folder_modules as $module_file => $data ) { - $module_info[ $module_file ] = _wordpoints_get_module_data_markup_translate( $module_file, $data ); - $module_info[ $module_file ]['is_uninstallable'] = is_uninstallable_wordpoints_module( $module ); + $module_info[ $module_file ] = _wordpoints_get_module_data_markup_translate( $module_file, $data ); + $module_info[ $module_file ]['is_uninstallable'] = is_uninstallable_wordpoints_module( $module ); - if ( ! $module_info[ $module_file ]['network'] ) { - $have_non_network_modules = true; - } + if ( ! $module_info[ $module_file ]['network'] ) { + $have_non_network_modules = true; } } } } + } - $modules_to_delete = count( $module_info ); + $modules_to_delete = count( $module_info ); - echo '

' . esc_html( _n( 'Delete module', 'Delete modules', $modules_to_delete, 'wordpoints' ) ) . '

'; + echo '

' . esc_html( _n( 'Delete module', 'Delete modules', $modules_to_delete, 'wordpoints' ) ) . '

'; + + if ( $have_non_network_modules && is_network_admin() ) { + wordpoints_show_admin_error( '' . esc_html__( 'Caution:', 'wordpoints' ) . '' . esc_html( _n( 'This module may be active on other sites in the network.', 'These modules may be active on other sites in the network.', $modules_to_delete, 'wordpoints' ) ) ); + } - if ( $have_non_network_modules && is_network_admin() ) { - wordpoints_show_admin_error( '' . esc_html__( 'Caution:', 'wordpoints' ) . '' . esc_html( _n( 'This module may be active on other sites in the network.', 'These modules may be active on other sites in the network.', $modules_to_delete, 'wordpoints' ) ) ); - } ?>

    ', wp_kses( sprintf( __( '%1$s by %2$s (will also delete its data)', 'wordpoints' ), esc_html( $module['name'] ), esc_html( $module['author_name'] ) ), array( 'strong' => array(), 'em' => array() ) ), ''; - $data_to_delete = true; + if ( $module['is_uninstallable'] ) { - } else { + /* translators: 1: module name, 2: module author */ + echo '
  • ', wp_kses( sprintf( __( '%1$s by %2$s (will also delete its data)', 'wordpoints' ), esc_html( $module['name'] ), esc_html( $module['author_name'] ) ), array( 'strong' => array(), 'em' => array() ) ), '
  • '; + $data_to_delete = true; - /* translators: 1: module name, 2: module author */ - echo '
  • ', wp_kses( sprintf( __( '%1$s by %2$s', 'wordpoints' ), esc_html( $module['name'] ), esc_html( $module['author_name'] ) ), array( 'strong' => array(), 'em' => array() ) ), '
  • '; - } + } else { + + /* translators: 1: module name, 2: module author */ + echo '
  • ', wp_kses( sprintf( __( '%1$s by %2$s', 'wordpoints' ), esc_html( $module['name'] ), esc_html( $module['author_name'] ) ), array( 'strong' => array(), 'em' => array() ) ), '
  • '; } + } + ?>
-

+

+ + ?> +

- '; - } - ?> + + +
@@ -395,11 +407,9 @@ function wordpoints_module_sandbox_scrape( $module ) {

@@ -431,7 +441,7 @@ function wordpoints_module_sandbox_scrape( $module ) { * * @since 1.1.0 */ - do_action( 'wordpoints_modules_screen-{$action}' ); + do_action( "wordpoints_modules_screen-{$action}" ); } add_screen_option( 'per_page', array( 'label' => _x( 'Modules', 'modules per page (screen options)', 'wordpoints' ), 'default' => 999 ) ); @@ -454,7 +464,7 @@ function wordpoints_module_sandbox_scrape( $module ) { 'title' => __( 'Troubleshooting', 'wordpoints' ), 'content' => '

' . esc_html__( 'Most of the time, modules play nicely with the core of WordPoints and with other modules. Sometimes, though, a module’s code will get in the way of another module, causing compatibility issues. If your site starts doing strange things, this may be the problem. Try deactivating all your modules and re-activating them in various combinations until you isolate which one(s) caused the issue.', 'wordpoints' ) . '

' . - '

' . sprintf( __( 'If something goes wrong with a module and you can’t use WordPoints, delete or rename that file in the %s directory and it will be automatically deactivated.', 'wordpoints' ), '' . esc_html( wordpoints_modules_dir() ) . '' ) . '

' + '

' . sprintf( esc_html__( 'If something goes wrong with a module and you can’t use WordPoints, delete or rename that file in the %s directory and it will be automatically deactivated.', 'wordpoints' ), '' . esc_html( wordpoints_modules_dir() ) . '' ) . '

' ) ); diff --git a/src/admin/screens/modules.php b/src/admin/screens/modules.php index 66645354..2b26beb7 100644 --- a/src/admin/screens/modules.php +++ b/src/admin/screens/modules.php @@ -31,7 +31,10 @@ $error_message = __( 'Module could not be activated because it triggered a fatal error.', 'wordpoints' ); - if ( isset( $_GET['module'], $_GET['_error_nonce'] ) && wp_verify_nonce( $_GET['_error_nonce'], "module-activation-error_{$_GET['module']}" ) ) { + if ( + isset( $_GET['_error_nonce'], $_GET['module'] ) + && wordpoints_verify_nonce( '_error_nonce', 'module-activation-error_%s', array( 'module' ) ) + ) { ?> @@ -80,7 +83,7 @@ wordpoints_show_admin_message( __( 'Selected modules deactivated.', 'wordpoints' ) ); -} elseif ( isset( $_REQUEST['action'] ) && 'update-selected' === $_REQUEST['action'] ) { +} elseif ( isset( $_REQUEST['action'] ) && 'update-selected' === sanitize_key( $_REQUEST['action'] ) ) { wordpoints_show_admin_message( esc_html__( 'No out of date modules were selected.', 'wordpoints' ) ); } diff --git a/src/assets/js/datatables-init.js b/src/assets/js/datatables-init.js index 1f58a61e..6fa9574d 100644 --- a/src/assets/js/datatables-init.js +++ b/src/assets/js/datatables-init.js @@ -5,10 +5,7 @@ * @since 1.0.0 */ -/* global WordPointsDataTable */ - -/** @global */ -WordPointsDataTable; +/* global WordPointsDataTable, jQuery */ jQuery( document ).ready( function () { diff --git a/src/components/points/admin/admin.php b/src/components/points/admin/admin.php index 53b5d76b..571e0407 100644 --- a/src/components/points/admin/admin.php +++ b/src/components/points/admin/admin.php @@ -48,11 +48,11 @@ function wordpoints_admin_register_scripts() { 'wordpoints-admin-points-hooks' , 'WordPointsHooksL10n' , array( - 'confirmDelete' => __( 'Are you sure that you want to delete this points type? This will delete all related logs and hooks.', 'wordpoints' ) - . ' ' . __( 'Once a points type has been deleted, you cannot bring it back.', 'wordpoints' ), - 'confirmTitle' => __( 'Are you sure?', 'wordpoints' ), - 'deleteText' => __( 'Delete', 'wordpoints' ), - 'cancelText' => __( 'Cancel', 'wordpoints' ), + 'confirmDelete' => esc_html__( 'Are you sure that you want to delete this points type? This will delete all related logs and hooks.', 'wordpoints' ) + . ' ' . esc_html__( 'Once a points type has been deleted, you cannot bring it back.', 'wordpoints' ), + 'confirmTitle' => esc_html__( 'Are you sure?', 'wordpoints' ), + 'deleteText' => esc_html__( 'Delete', 'wordpoints' ), + 'cancelText' => esc_html__( 'Cancel', 'wordpoints' ), ) ); } @@ -195,7 +195,7 @@ function wordpoints_admin_points_hooks_screen_options( $screen_options, $screen case 'wordpoints_page_wordpoints_points_hooks': $url = admin_url( $path ); - // fallthru + // fallthru case 'wordpoints_page_wordpoints_points_hooks-network': if ( ! isset( $url ) ) { @@ -332,7 +332,15 @@ function wordpoints_points_profile_options( $user ) { foreach ( wordpoints_get_points_types() as $slug => $type ) { - echo esc_html( $type['name'] ) . ': ' . wordpoints_format_points( wordpoints_get_points( $user->ID, $slug ), $slug, 'profile_page' ) . '
'; + echo esc_html( $type['name'] ) . ': '; + + wordpoints_display_points( + wordpoints_get_points( $user->ID, $slug ) + , $slug + , 'profile_page' + ); + + echo '
'; } } } @@ -358,16 +366,33 @@ function wordpoints_points_profile_options_update( $user_id ) { if ( ! isset( $_POST['wordpoints_points_set_nonce'], $_POST['wordpoints_set_reason'] ) - || ! wp_verify_nonce( $_POST['wordpoints_points_set_nonce'], 'wordpoints_points_set_profile' ) + || ! wordpoints_verify_nonce( 'wordpoints_points_set_nonce', 'wordpoints_points_set_profile', null, 'post' ) ) { return; } foreach ( wordpoints_get_points_types() as $slug => $type ) { - if ( isset( $_POST[ "wordpoints_points_set-{$slug}" ], $_POST[ "wordpoints_points-{$slug}" ], $_POST[ "wordpoints_points_old-{$slug}" ] ) ) { - - wordpoints_alter_points( $user_id, $_POST[ "wordpoints_points-{$slug}" ] - $_POST[ "wordpoints_points_old-{$slug}" ], $slug, 'profile_edit', array( 'user_id' => get_current_user_id(), 'reason' => esc_html( wp_unslash( $_POST['wordpoints_set_reason'] ) ) ) ); + if ( + isset( + $_POST[ "wordpoints_points_set-{$slug}" ] + , $_POST[ "wordpoints_points-{$slug}" ] + , $_POST[ "wordpoints_points_old-{$slug}" ] + ) + && wordpoints_int( $_POST[ "wordpoints_points-{$slug}" ] ) + && wordpoints_int( $_POST[ "wordpoints_points_old-{$slug}" ] ) + ) { + + wordpoints_alter_points( + $user_id + , (int) $_POST[ "wordpoints_points-{$slug}" ] - (int) $_POST[ "wordpoints_points_old-{$slug}" ] + , $slug + , 'profile_edit' + , array( + 'user_id' => get_current_user_id(), + 'reason' => wp_unslash( esc_html( $_POST['wordpoints_set_reason'] ) ) + ) + ); } } } @@ -438,4 +463,27 @@ function wordpoints_points_admin_settings_save() { } add_action( 'wordpoints_admin_settings_update', 'wordpoints_points_admin_settings_save' ); +/** + * Display notices to the user on the administration panels. + * + * @since 1.9.0 + */ +function wordpoints_points_admin_notices() { + + if ( + ( ! isset( $_GET['page'] ) || 'wordpoints_points_hooks' !== $_GET['page'] ) + && current_user_can( 'manage_wordpoints_points_types' ) + && ! wordpoints_get_points_types() + ) { + + wordpoints_show_admin_message( + sprintf( + __( 'Welcome to WordPoints! Get started by creating a points type.', 'wordpoints' ) + , esc_attr( self_admin_url( 'admin.php?page=wordpoints_points_hooks' ) ) + ) + ); + } +} +add_action( 'admin_notices', 'wordpoints_points_admin_notices' ); + // EOF diff --git a/src/components/points/admin/includes/ajax.php b/src/components/points/admin/includes/ajax.php index f51627af..0031b7d8 100644 --- a/src/components/points/admin/includes/ajax.php +++ b/src/components/points/admin/includes/ajax.php @@ -172,7 +172,7 @@ function wordpoints_ajax_save_points_hook() { wp_die( $error ); } - $number = $_POST['multi_number']; + $number = (int) $_POST['multi_number']; $hook_id = $id_base . '-' . $number; } @@ -181,7 +181,7 @@ function wordpoints_ajax_save_points_hook() { $settings = false; if ( isset( $_POST[ 'hook-' . $id_base ] ) && is_array( $_POST[ 'hook-' . $id_base ] ) ) { - $settings = wp_unslash( $_POST[ 'hook-' . $id_base ] ); + $settings = wp_unslash( $_POST[ 'hook-' . $id_base ] ); // XSS pass WPCS. } $points_types_hooks = WordPoints_Points_Hooks::get_points_types_hooks(); diff --git a/src/components/points/admin/screens/hooks-load.php b/src/components/points/admin/screens/hooks-load.php index 5cd585dc..ec9eb1f6 100644 --- a/src/components/points/admin/screens/hooks-load.php +++ b/src/components/points/admin/screens/hooks-load.php @@ -45,7 +45,7 @@ && wp_verify_nonce( $_GET['wordpoints-accessiblity-nonce'], 'wordpoints_points_hooks_accessiblity' ) ) { - $accessibility_mode = ( 'on' === $_GET['accessibility-mode'] ) ? 'on' : 'off'; + $accessibility_mode = ( 'on' === sanitize_key( $_GET['accessibility-mode'] ) ) ? 'on' : 'off'; set_user_setting( 'wordpoints_points_hooks_access', $accessibility_mode ); } diff --git a/src/components/points/admin/screens/hooks-no-js-load.php b/src/components/points/admin/screens/hooks-no-js-load.php index dd45ff56..4b9d1388 100644 --- a/src/components/points/admin/screens/hooks-no-js-load.php +++ b/src/components/points/admin/screens/hooks-no-js-load.php @@ -53,7 +53,7 @@ $hook = WordPoints_Points_Hooks::get_handler_by_id_base( $id_base ); -if ( isset( $_POST['removehook'] ) && $_POST['removehook'] ) { +if ( ! empty( $_POST['removehook'] ) ) { // - We are deleting an instance of a hook. @@ -90,7 +90,7 @@ } else { if ( isset( $_POST[ 'hook-' . $id_base ] ) && is_array( $_POST[ 'hook-' . $id_base ] ) ) { - $new_instance = wp_unslash( reset( $_POST[ 'hook-' . $id_base ] ) ); + $new_instance = wp_unslash( reset( $_POST[ 'hook-' . $id_base ] ) ); // XSS pass WPCS. } $number = $hook->get_number_by_id( $hook_id ); diff --git a/src/components/points/admin/screens/hooks-no-js.php b/src/components/points/admin/screens/hooks-no-js.php index 86702d55..2df9bb4b 100644 --- a/src/components/points/admin/screens/hooks-no-js.php +++ b/src/components/points/admin/screens/hooks-no-js.php @@ -36,7 +36,9 @@ if ( isset( $_GET['base'], $_GET['num'] ) ) { // Copy minimal info from an existing instance of this hook to a new instance. - $hook = WordPoints_Points_Hooks::get_handler_by_id_base( $_GET['base'] ); + $hook = WordPoints_Points_Hooks::get_handler_by_id_base( + sanitize_key( $_GET['base'] ) + ); if ( ! $hook ) { diff --git a/src/components/points/admin/screens/hooks.php b/src/components/points/admin/screens/hooks.php index d5d9ca6d..9a07a077 100644 --- a/src/components/points/admin/screens/hooks.php +++ b/src/components/points/admin/screens/hooks.php @@ -10,8 +10,8 @@ if ( current_user_can( 'manage_wordpoints_points_types' ) ) { if ( - isset( $_POST['add_new'], $_POST['save-points-type'] ) - && wp_verify_nonce( $_POST['add_new'], 'wordpoints_add_new_points_type' ) + isset( $_POST['save-points-type'], $_POST['points-name'], $_POST['points-prefix'], $_POST['points-suffix'] ) + && wordpoints_verify_nonce( 'add_new', 'wordpoints_add_new_points_type', null, 'post' ) ) { // - We are creating a new points type. @@ -32,8 +32,8 @@ } elseif ( ! empty( $_POST['delete-points-type'] ) - && isset( $_POST['delete-points-type-nonce'], $_POST['points-slug'] ) - && wp_verify_nonce( $_POST['delete-points-type-nonce'], "wordpoints_delete_points_type-{$_POST['points-slug']}" ) + && isset( $_POST['points-slug'] ) + && wordpoints_verify_nonce( 'delete-points-type-nonce', 'wordpoints_delete_points_type-%s', array( 'points-slug' ), 'post' ) ) { // - We are deleting a points type. diff --git a/src/components/points/admin/screens/logs.php b/src/components/points/admin/screens/logs.php index 3335bf9e..0882e6b7 100644 --- a/src/components/points/admin/screens/logs.php +++ b/src/components/points/admin/screens/logs.php @@ -21,69 +21,69 @@ create a type of points before you can use this page.', 'wordpoints' ), 'admin.php?page=wordpoints_points_hooks' ) ); + wordpoints_show_admin_error( sprintf( __( 'You need to create a type of points before you can use this page.', 'wordpoints' ), 'admin.php?page=wordpoints_points_hooks' ) ); - } else { + } else { + + // Show a tab for each points type. + $tabs = array(); + + foreach ( $points_types as $slug => $settings ) { + + $tabs[ $slug ] = $settings['name']; + } + + wordpoints_admin_show_tabs( $tabs, false ); - // Show a tab for each points type. - $tabs = array(); - - foreach ( $points_types as $slug => $settings ) { - - $tabs[ $slug ] = $settings['name']; - } - - wordpoints_admin_show_tabs( $tabs, false ); - - if ( is_network_admin() ) { - $query = 'network'; - } else { - $query = 'default'; - } - - $current_type = wordpoints_admin_get_current_tab( $tabs ); - - /** - * At the top of one of the tabs on the points logs admin panel. - * - * @since 1.3.0 - * - * @param string $points_type The points type the current tab is for. - * @param string $query The current logs query being performed. - */ - do_action( 'wordpoints_admin_points_logs_tab', $current_type, $query ); - - // Get and display the logs based on current points type. - wordpoints_show_points_logs_query( $current_type, $query ); - - /** - * At the bottom of one of the tabs on the points logs admin panel. - * - * @since 1.3.0 - * - * @param string $points_type The points type the current tab is for. - * @param string $query The current logs query being performed. - */ - do_action( 'wordpoints_admin_points_logs_tab_after', $current_type, $query ); + if ( is_network_admin() ) { + $query = 'network'; + } else { + $query = 'default'; } + $current_type = wordpoints_admin_get_current_tab( $tabs ); + /** - * After points logs on administration panel. + * At the top of one of the tabs on the points logs admin panel. + * + * @since 1.3.0 + * + * @param string $points_type The points type the current tab is for. + * @param string $query The current logs query being performed. + */ + do_action( 'wordpoints_admin_points_logs_tab', $current_type, $query ); + + // Get and display the logs based on current points type. + wordpoints_show_points_logs_query( $current_type, $query ); + + /** + * At the bottom of one of the tabs on the points logs admin panel. + * + * @since 1.3.0 * - * @since 1.0.0 + * @param string $points_type The points type the current tab is for. + * @param string $query The current logs query being performed. */ - do_action( 'wordpoints_admin_points_logs_after' ); + do_action( 'wordpoints_admin_points_logs_tab_after', $current_type, $query ); + } + + /** + * After points logs on administration panel. + * + * @since 1.0.0 + */ + do_action( 'wordpoints_admin_points_logs_after' ); ?> diff --git a/src/components/points/includes/class-un-installer.php b/src/components/points/includes/class-un-installer.php index 083def32..6177ce62 100644 --- a/src/components/points/includes/class-un-installer.php +++ b/src/components/points/includes/class-un-installer.php @@ -32,6 +32,7 @@ class WordPoints_Points_Un_Installer extends WordPoints_Un_Installer_Base { '1.5.0' => array( /* - */ 'site' => true /* - */ ), '1.5.1' => array( 'single' => true, /* - */ 'network' => true ), '1.8.0' => array( /* - */ 'site' => true /* - */ ), + '1.9.0' => array( 'single' => true, 'site' => true, 'network' => true ), ); /** @@ -105,17 +106,6 @@ protected function before_uninstall() { protected function before_update() { if ( 1 === version_compare( '1.4.0', $this->updating_from ) ) { - - // If we're updating to 1.4.0, we initialize the hooks early, because - // we use them during the update. - remove_action( 'wordpoints_modules_loaded', array( 'WordPoints_Points_Hooks', 'initialize_hooks' ) ); - WordPoints_Points_Hooks::initialize_hooks(); - - // Default to network mode off during the tests, but save the current - // mode so we can restore it afterward. - $this->points_hooks_network_mode = WordPoints_Points_Hooks::get_network_mode(); - WordPoints_Points_Hooks::set_network_mode( false ); - add_filter( 'wordpoints_points_hook_update_callback', array( $this, '_1_4_0_clean_hook_settings' ), 10, 4 ); } @@ -131,6 +121,28 @@ protected function before_update() { if ( $this->network_wide ) { unset( $this->updates['1_8_0'] ); } + + if ( 1 === version_compare( '1.9.0', $this->updating_from ) ) { + + // If we're updating to 1.4.0, we initialize the hooks early, because + // we use them during the update. + remove_action( 'wordpoints_modules_loaded', array( 'WordPoints_Points_Hooks', 'initialize_hooks' ) ); + + WordPoints_Points_Hooks::register( + 'WordPoints_Comment_Removed_Points_Hook' + ); + + WordPoints_Points_Hooks::register( + 'WordPoints_Post_Delete_Points_Hook' + ); + + WordPoints_Points_Hooks::initialize_hooks(); + + // Default to network mode off during the tests, but save the current + // mode so we can restore it afterward. + $this->points_hooks_network_mode = WordPoints_Points_Hooks::get_network_mode(); + WordPoints_Points_Hooks::set_network_mode( false ); + } } /** @@ -480,11 +492,13 @@ protected function _1_4_0_split_points_hooks( $hook, $new_hook, $key, $split_key if ( WordPoints_Points_Hooks::get_network_mode() ) { $hook_type = 'network'; + $network_ = 'network_'; } else { $hook_type = 'standard'; + $network_ = ''; } - $new_hook = WordPoints_Points_Hooks::get_handler_by_id_base( $new_hook ); + $new_hook = WordPoints_Points_Hooks::get_handler_by_id_base( $new_hook ); $hook = WordPoints_Points_Hooks::get_handler_by_id_base( $hook ); $points_types_hooks = WordPoints_Points_Hooks::get_points_types_hooks(); @@ -514,11 +528,7 @@ protected function _1_4_0_split_points_hooks( $hook, $new_hook, $key, $split_key ); // Make sure the correct points type is retrieved for network hooks. - if ( 'network' === $hook_type ) { - $points_type = $hook->points_type( 'network_' . $number ); - } else { - $points_type = $hook->points_type( $number ); - } + $points_type = $hook->points_type( $network_ . $number ); // Add this instance to the points-types-hooks list. $points_types_hooks[ $points_type ][] = $new_hook->get_id( $number ); @@ -653,6 +663,148 @@ protected function update_single_to_1_5_1() { protected function update_site_to_1_8_0() { $this->add_installed_site_id(); } + + /** + * Update a network to 1.9.0. + * + * @since 1.9.0 + */ + protected function update_network_to_1_9_0() { + + if ( $this->network_wide ) { + + // Combine the network-wide points hooks. + $network_mode = WordPoints_Points_Hooks::get_network_mode(); + WordPoints_Points_Hooks::set_network_mode( true ); + $this->_1_9_0_combine_hooks( 'comment', 'comment_removed' ); + $this->_1_9_0_combine_hooks( 'post', 'post_delete' ); + WordPoints_Points_Hooks::set_network_mode( $network_mode ); + } + } + + /** + * Update a site to 1.9.0. + * + * @since 1.9.0 + */ + protected function update_site_to_1_9_0() { + $this->_1_9_0_combine_hooks( 'comment', 'comment_removed' ); + $this->_1_9_0_combine_hooks( 'post', 'post_delete' ); + } + + /** + * Update a single site to 1.9.0. + * + * @since 1.9.0 + */ + protected function update_single_to_1_9_0() { + $this->_1_9_0_combine_hooks( 'comment', 'comment_removed' ); + $this->_1_9_0_combine_hooks( 'post', 'post_delete' ); + } + + /** + * Combine any Comment/Comment Removed or Post/Post Delete hook instance pairs. + * + * @since 1.9.0 + * + * @param string $type The primary hook type that awards the points. + * @param string $reverse_type The counterpart hook type that reverses points. + */ + protected function _1_9_0_combine_hooks( $type, $reverse_type ) { + + $hook = WordPoints_Points_Hooks::get_handler_by_id_base( + "wordpoints_{$type}_points_hook" + ); + $reverse_hook = WordPoints_Points_Hooks::get_handler_by_id_base( + "wordpoints_{$reverse_type}_points_hook" + ); + + if ( WordPoints_Points_Hooks::get_network_mode() ) { + $hook_type = 'network'; + $network_ = 'network_'; + } else { + $hook_type = 'standard'; + $network_ = ''; + } + + $hook_instances = $hook->get_instances( $hook_type ); + $hook_reverse_instances = $reverse_hook->get_instances( $hook_type ); + + $default_points = ( 'post' === $hook_type ) ? 20 : 10; + $defaults = array( 'points' => $default_points, 'post_type' => 'ALL' ); + + // Get the hooks into an array that is indexed by post type and the + // number of points. This allows us to easily check for any counterparts when + // we loop through the reverse type hooks below. It is even safe if a user + // is doing something crazy like multiple hooks for the same post type. + $hook_instances_indexed = array(); + + foreach ( $hook_instances as $number => $instance ) { + + $instance = array_merge( $defaults, $instance ); + + $hook_instances_indexed + [ $hook->points_type( $network_ . $number ) ] + [ $instance['post_type'] ] + [ $instance['points'] ] + [] = $number; + } + + foreach ( $hook_reverse_instances as $number => $instance ) { + + $instance = array_merge( $defaults, $instance ); + + $points_type = $reverse_hook->points_type( $network_ . $number ); + + // We use empty() instead of isset() because array_pop() below may leave + // us with an empty array as the value. + if ( empty( $hook_instances_indexed[ $points_type ][ $instance['post_type'] ][ $instance['points'] ] ) ) { + continue; + } + + $comment_instance_number = array_pop( + $hook_instances_indexed[ $points_type ][ $instance['post_type'] ][ $instance['points'] ] + ); + + // We need to unset this instance from the list of hook instances. It + // is expected for it to be automatically reversed, and that is the + // default setting. If we don't unset it here it will get auto-reversal + // turned off below, which isn't what we want. + unset( $hook_instances[ $comment_instance_number ] ); + + // Now we can just delete this reverse hook instance. + $reverse_hook->delete_callback( + $reverse_hook->get_id( $number ) + ); + } + + // Any hooks left in the array are not paired with a reverse type hook, and + // aren't expected to auto-reverse, so we need to turn their auto-reversal + // setting off. + if ( ! empty( $hook_instances ) ) { + + foreach ( $hook_instances as $number => $instance ) { + $instance['auto_reverse'] = 0; + $hook->update_callback( $instance, $number ); + } + + // We add a flag to the database so we'll know to enable legacy features. + update_site_option( + "wordpoints_{$type}_hook_legacy" + , true + ); + } + + // Now we check if there are any unpaired reverse typ hooks. If there are + // we'll set this flag in the database that will keep some legacy features + // enabled. + if ( $reverse_hook->get_instances( $hook_type ) ) { + update_site_option( + "wordpoints_{$reverse_type}_hook_legacy" + , true + ); + } + } } return 'WordPoints_Points_Un_Installer'; diff --git a/src/components/points/includes/class-wordpoints-points-hook.php b/src/components/points/includes/class-wordpoints-points-hook.php index 764242ec..6cef5ca0 100644 --- a/src/components/points/includes/class-wordpoints-points-hook.php +++ b/src/components/points/includes/class-wordpoints-points-hook.php @@ -260,7 +260,8 @@ final public function get_id( $number = null ) { * * @since 1.4.0 * - * @return int|false The current hook number, or false. + * @return int|string|false The current hook number, the '__i__' placeholder, or + * false. */ final public function get_number() { @@ -277,6 +278,14 @@ final public function get_number() { final public function set_number( $instance_id ) { $this->number = $this->get_number_by_id( $instance_id ); + + if ( '__i__' === $this->number ) { + return; + } elseif ( '0' === $this->number ) { + $this->number = 0; + } else { + wordpoints_posint( $this->number ); + } } /** @@ -353,6 +362,21 @@ final public function set_options( $options ) { $this->options = $options; } + /** + * Set a particular option. + * + * This method will probably be declared final in WordPoints 2.0. + * + * @since 1.9.0 + * + * @param string $option The index for the option. + * @param mixed $value The value to assign to this option. + */ + /*final*/ public function set_option( $option, $value ) { + + $this->options[ $option ] = $value; + } + /** * Calculate the ID number of the next instance of a hook. * diff --git a/src/components/points/includes/class-wordpoints-points-hooks.php b/src/components/points/includes/class-wordpoints-points-hooks.php index 8f836e2c..039140d5 100644 --- a/src/components/points/includes/class-wordpoints-points-hooks.php +++ b/src/components/points/includes/class-wordpoints-points-hooks.php @@ -658,10 +658,12 @@ public static function points_type_form( $slug = null, $wrap = 'hook' ) {
"delete_points_type-{$slug}" ) ); - } + + if ( ! $add_new ) { + wp_nonce_field( "wordpoints_delete_points_type-{$slug}", 'delete-points-type-nonce' ); + submit_button( _x( 'Delete', 'points type', 'wordpoints' ), 'delete', 'delete-points-type', false, array( 'id' => "delete_points_type-{$slug}" ) ); + } + ?>
diff --git a/src/components/points/includes/class-wordpoints-points-logs-query.php b/src/components/points/includes/class-wordpoints-points-logs-query.php index b2d9ba43..628562b4 100644 --- a/src/components/points/includes/class-wordpoints-points-logs-query.php +++ b/src/components/points/includes/class-wordpoints-points-logs-query.php @@ -273,7 +273,7 @@ public function __construct( $args = array() ) { if ( isset( $this->_args['meta_query'][ $key ] ) ) { unset( $this->_args['meta_query'][ $key ] ); - _deprecated_argument( __METHOD__, '1.1.0', sprintf( esc_html__( '%s is no longer supported.', 'wordpoints' ), "\$args['meta_query'][{$key}]" ) ); + _deprecated_argument( __METHOD__, '1.1.0', sprintf( '%s is no longer supported.', "\$args['meta_query'][{$key}]" ) ); } } @@ -281,7 +281,7 @@ public function __construct( $args = array() ) { $this->_args['meta_key'] = $this->_args['meta_query']['key']; unset( $this->_args['meta_query']['key'] ); - _deprecated_argument( __METHOD__, '1.1.0', sprintf( esc_html__( '%s has been replaced by %s.', 'wordpoints' ), '$args["meta_query"]["key"]', '$args["meta_key"]' ) ); + _deprecated_argument( __METHOD__, '1.1.0', sprintf( '%s has been replaced by %s.', '$args["meta_query"]["key"]', '$args["meta_key"]' ) ); } foreach ( array( 'value', 'value__in', 'value__not_in' ) as $key ) { @@ -290,7 +290,7 @@ public function __construct( $args = array() ) { $this->_args['meta_value'] = $this->_args['meta_query'][ $key ]; unset( $this->_args['meta_query'][ $key ] ); - _deprecated_argument( __METHOD__, '1.1.0', sprintf( esc_html__( '%s has been replaced by %s.', 'wordpoints' ), "\$args['meta_query'][{$key}]", '$args["meta_value"]' ) ); + _deprecated_argument( __METHOD__, '1.1.0', sprintf( '%s has been replaced by %s.', "\$args['meta_query'][{$key}]", '$args["meta_value"]' ) ); if ( 'value__not_in' === $key ) { $this->_args['meta_compare'] = 'NOT IN'; @@ -303,6 +303,22 @@ public function __construct( $args = array() ) { } // public function __construct() + /** + * Get a query arg. + * + * @since 1.9.0 + * + * @param string $arg The query arg whose value to retrieve. + * + * @return mixed|void The query arg's value, or nothing if it isn't set. + */ + public function get_arg( $arg ) { + + if ( isset( $this->_args[ $arg ] ) ) { + return $this->_args[ $arg ]; + } + } + /** * Set arguments for the query. * @@ -399,6 +415,10 @@ public function get( $method = 'results', $use_cache = true ) { if ( $this->_is_cached_query ) { $this->_cache_set( $result, "get_{$method}" ); + + if ( 'results' === $method || 'col' === $method ) { + $this->_cache_set( count( $result ), 'count' ); + } } return $result; @@ -432,7 +452,7 @@ public function get_page( $page, $per_page = 25 ) { $start = ( $page - 1 ) * $per_page; - // First try the cache. + // First try the main cache. if ( $this->_is_cached_query ) { $cache = $this->_cache_get( 'get_results' ); @@ -464,7 +484,9 @@ public function get_page( $page, $per_page = 25 ) { $this->_select_type = 'SELECT'; - $results = $this->_get( 'results' ); + unset( $this->_cache_query_md5 ); + + $results = $this->get(); // Restore the originial arguments. $this->_args = $args; @@ -473,6 +495,8 @@ public function get_page( $page, $per_page = 25 ) { $this->_limit = ''; $this->_prepare_limit(); + unset( $this->_cache_query_md5 ); + return $results; } @@ -504,13 +528,11 @@ public function get_sql( $select_type = null ) { /** * Prime the cache. * - * Calling this function will pre-fill this instance's cache from the object - * cache. Not all queries are cached, only those for which this method is called. - * If you want your query to be cached, then you should call this function - * immediately after constructing the new query. - * - * If the results aren't found in the cache, the query will be run and the - * results cached. + * Calling this function will cause this query to be cached, and if the results + * are already in the object cache they will be returned instead of a new call + * to the database being made. Not all queries are cached, only those for which + * this method is called. If you want your query to be cached, then you should + * call this function immediately after constructing the new query. * * The $key passed is used as the cache key in the 'wordpoints_points_logs_query' * cache group. Multiple queries can use the same key, and you are encouraged to @@ -534,24 +556,27 @@ public function get_sql( $select_type = null ) { * automatically by wordpoints_clean_points_logs_cache() when a new matching log * is added to the database. * - * The $methods paramater determies which methods of retrieving the data will be - * cached. To cache multiple methods, pass an array. Priming the 'results' method - * will automatically cache the 'count' as well, by counting the results. - * However, if you are only going to use the count, you should specify 'count' - * instead, to avoid pulling unneeded data from the database into the cache. - * * The $network parameter determines whether the query will be cached in a global * cache group (for the entire network) or per-site. This is a moot point except * on multisite installs. * * @since 1.5.0 + * @since 1.9.0 No longer runs any database queries to fill the cache if it is empty. + * @since 1.9.0 The $methods paramter was deprecated and is no longer used. * - * @param string $key The cache key to use. - * @param string $methods The query method(s) to cache, 'results' (default), - * 'var', 'col', 'row', or 'count'. - * @param string $network Whether this is a network-wide query. + * @param string $key The cache key to use. + * @param string $deprecated Deprecated; no longer used. + * @param string $network Whether this is a network-wide query. */ - public function prime_cache( $key = 'default:%points_type%', $methods = 'results', $network = false ) { + public function prime_cache( $key = 'default:%points_type%', $deprecated = null, $network = false ) { + + if ( ! is_null( $deprecated ) ) { + _deprecated_argument( + __METHOD__ + , '1.9.0' + , 'The $method argument is deprecated and should no longer be used.' + ); + } $this->_is_cached_query = true; @@ -572,52 +597,7 @@ public function prime_cache( $key = 'default:%points_type%', $methods = 'results } else { $this->_cache_group = 'wordpoints_points_logs_query'; } - - $cache = $this->_cache_get(); - - if ( ! is_array( $cache ) ) { - $cache = array(); - } - - $methods = array_unique( (array) $methods ); - - foreach ( $methods as $method ) { - - if ( 'count' !== $method ) { - $method_key = "get_{$method}"; - } else { - $method_key = 'count'; - } - - if ( isset( $cache[ $method_key ] ) ) { - continue; - } - - switch ( $method ) { - - case 'results': - $cache['get_results'] = $this->get(); - $cache['count'] = count( $cache['get_results'] ); - break; - - case 'count': - $cache['count'] = $this->count(); - break; - - case 'var': - case 'col': - case 'row': - $cache[ "get_{$method}" ] = $this->get( $method ); - break; - - default: - return; - } - } - - $this->_cache_set( $cache ); - - } // public function prime_cache() + } /** * Filter date query valid columns for WP_Date_Query. diff --git a/src/components/points/includes/hooks/abstracts/comment-approved.php b/src/components/points/includes/hooks/abstracts/comment-approved.php index 3826c74a..9d2fdc1a 100644 --- a/src/components/points/includes/hooks/abstracts/comment-approved.php +++ b/src/components/points/includes/hooks/abstracts/comment-approved.php @@ -21,16 +21,11 @@ abstract class WordPoints_Comment_Approved_Points_Hook_Base extends WordPoints_P * * @type array $defaults */ - protected $defaults = array( 'points' => 10, 'post_type' => 'ALL' ); - - /** - * The log type of the logs of this hook. - * - * @since 1.8.0 - * - * @type string $log_type - */ - protected $log_type; + protected $defaults = array( + 'points' => 10, + 'post_type' => 'ALL', + 'auto_reverse' => 1, + ); /** * Initialize the hook. @@ -45,19 +40,13 @@ public function __construct( $title, $args ) { $args = array_merge( $defaults, $args ); - parent::init( $title, $args ); + parent::__construct( $title, $args ); add_action( 'transition_comment_status', array( $this, 'hook' ), 10, 3 ); add_action( 'transition_comment_status', array( $this, 'reverse_hook' ), 10, 3 ); add_action( 'wp_insert_comment', array( $this, 'new_comment_hook' ), 10, 2 ); - add_filter( "wordpoints_points_log-{$this->log_type}", array( $this, 'logs' ), 10, 6 ); - add_filter( "wordpoints_points_log-reverse_{$this->log_type}", array( $this, 'logs' ), 10, 6 ); - add_action( 'delete_comment', array( $this, 'clean_logs_on_comment_deletion' ) ); - add_action( 'delete_post', array( $this, 'clean_logs_on_post_deletion' ) ); - - add_filter( "wordpoints_user_can_view_points_log-{$this->log_type}", array( $this, 'user_can_view' ), 10, 2 ); } /** @@ -192,49 +181,34 @@ public function reverse_hook( $new_status, $old_status, $comment ) { return; } - // Get a list of transactions to reverse. - $query = new WordPoints_Points_Logs_Query( + $post_type = get_post_field( 'post_type', $comment->comment_post_ID ); + if ( ! $this->should_do_auto_reversals_for_post_type( $post_type ) ) { + return; + } + + $logs = $this->get_logs_to_auto_reverse( array( - 'log_type' => $this->log_type, - 'meta_query' => array( - array( - 'key' => 'comment_id', - 'value' => $comment->comment_ID, - ), - ), + 'key' => 'comment_id', + 'value' => $comment->comment_ID, ) ); - $logs = $query->get(); - - if ( ! $logs ) { - return; - } - foreach ( $logs as $log ) { - $comment_id = wordpoints_get_points_log_meta( $log->id, 'comment_id', true ); - $meta_key = $this->get_last_status_comment_meta_key( $log->points_type ); - if ( 'approved' !== get_comment_meta( $comment_id, $meta_key, true ) ) { + if ( 'approved' !== get_comment_meta( $comment->comment_ID, $meta_key, true ) ) { continue; } - wordpoints_alter_points( - $log->user_id - , -$log->points - , $log->points_type - , "reverse_{$this->log_type}" - , array( 'original_log_id' => $log->id ) - ); + $this->auto_reverse_log( $log ); delete_comment_meta( $comment->comment_ID, $meta_key ); } } /** - * Select which use to award points to. + * Select which user to award points to. * * Overriding this function gives you the ability to choose whether to award the * points to the comment author (the default), the post author, or even someone @@ -283,36 +257,15 @@ public function logs( $text, $points, $points_type, $user_id, $log_type, $meta ) if ( ! $comment ) { - $post = false; - - if ( isset( $meta['post_id'] ) ) { - $post = get_post( $meta['post_id'] ); - } - - if ( $post ) { - - $post_title = get_the_title( $post->ID ); - - $link = '' - . ( $post_title ? $post_title : _x( '(no title)', 'post title', 'wordpoints' ) ) - . ''; - - $text = sprintf( $this->get_option( 'log_text_post_title' . $reverse ), $link ); - - } else { - - $text = $this->get_option( 'log_text_no_post_title' . $reverse ); - } + $text = parent::logs( $text, $points, $points_type, $user_id, $log_type, $meta ); } else { - $post_title = get_the_title( $comment->comment_post_ID ); - $link = '' - . ( $post_title ? $post_title : _x( '(no title)', 'post title', 'wordpoints' ) ) - . ''; - - /* translators: %s will be the post's title. */ - $text = sprintf( $this->get_option( 'log_text_post_title' . $reverse ), $link ); + $text = $this->log_with_post_title_link( + $comment->comment_post_ID + , $reverse + , get_comment_link( $comment ) + ); } return $text; @@ -357,8 +310,6 @@ protected function get_last_status_comment_meta_key( $points_type ) { */ public function clean_logs_on_comment_deletion( $comment_id ) { - global $wpdb; - $query = new WordPoints_Points_Logs_Query( array( 'log_type' => $this->log_type, @@ -396,50 +347,6 @@ public function clean_logs_on_comment_deletion( $comment_id ) { wordpoints_regenerate_points_logs( $logs ); } - /** - * Clean the logs when a post is deleted. - * - * Cleans the metadata for any logs related to the post being deleted. The post - * ID meta field is deleted from the database. Once the metadata is cleaned up, - * the logs are regenerated. - * - * @since 1.8.0 - * - * @action delete_post Added by the constructor. - * - * @param int $post_id The ID of the post being deleted. - * - * return void - */ - public function clean_logs_on_post_deletion( $post_id ) { - - global $wpdb; - - $query = new WordPoints_Points_Logs_Query( - array( - 'log_type' => $this->log_type, - 'meta_query' => array( - array( - 'key' => 'post_id', - 'value' => $post_id, - ), - ), - ) - ); - - $logs = $query->get(); - - if ( ! $logs ) { - return; - } - - foreach ( $logs as $log ) { - wordpoints_delete_points_log_meta( $log->id, 'post_id' ); - } - - wordpoints_regenerate_points_logs( $logs ); - } - /** * Check if a user can view a particular log entry. * diff --git a/src/components/points/includes/hooks/abstracts/post-type.php b/src/components/points/includes/hooks/abstracts/post-type.php index a3a9b8d5..17af69a5 100644 --- a/src/components/points/includes/hooks/abstracts/post-type.php +++ b/src/components/points/includes/hooks/abstracts/post-type.php @@ -16,6 +16,34 @@ */ abstract class WordPoints_Post_Type_Points_Hook_Base extends WordPoints_Points_Hook { + /** + * The log type of the logs of this hook. + * + * @since 1.9.0 + * + * @type string $log_type + */ + protected $log_type; + + /** + * Initialize the hook. + * + * @since 1.9.0 + * + * @see WordPoints_Points_Hook::init() + */ + public function __construct( $title, $args ) { + + parent::init( $title, $args ); + + add_filter( "wordpoints_points_log-{$this->log_type}", array( $this, 'logs' ), 10, 6 ); + add_filter( "wordpoints_points_log-reverse_{$this->log_type}", array( $this, 'logs' ), 10, 6 ); + + add_action( 'delete_post', array( $this, 'clean_logs_on_post_deletion' ), 15 ); + + add_filter( "wordpoints_user_can_view_points_log-{$this->log_type}", array( $this, 'user_can_view' ), 10, 2 ); + } + /** * Check if the post type setting matches a certian post type. * @@ -38,6 +66,195 @@ public function is_matching_post_type( $post_type, $instance_post_type ) { ); } + /** + * Check if automatic reversals should be performed for a particular post type. + * + * @since 1.9.0 + * + * @param int $post_type The slug of a post type. + * + * @return bool True if auto-reversals should be done; false otherwise. + */ + protected function should_do_auto_reversals_for_post_type( $post_type ) { + + // Check if this hook type allows auto-reversals to be disabled. + if ( ! $this->get_option( 'disable_auto_reverse_label' ) ) { + return true; + } + + $instances = $this->get_instances(); + + foreach ( $instances as $instance ) { + + $instance = array_merge( $this->defaults, $instance ); + + if ( $post_type === $instance['post_type'] ) { + return ! empty( $instance['auto_reverse'] ); + } elseif ( 'ALL' === $instance['post_type'] ) { + $all_posts_instance = $instance; + } + } + + return ! empty( $all_posts_instance['auto_reverse'] ); + } + + /** + * Get the logs to auto-reverse. + * + * @since 1.9.0 + * + * @param array $meta_query Meta query arguments to use in the search. + * + * @return array The logs to reverse. + */ + protected function get_logs_to_auto_reverse( array $meta_query ) { + + $query = new WordPoints_Points_Logs_Query( + array( + 'log_type' => $this->log_type, + 'meta_query' => array( + $meta_query, + array( + 'key' => 'auto_reversed', + 'compare' => 'NOT EXISTS', + 'value' => 'see bug #23268 (fixed in 3.9)', + ), + ), + ) + ); + + $logs = $query->get(); + + if ( ! $logs ) { + return array(); + } + + return $logs; + } + + /** + * Automatically reverse a transaction. + * + * @since 1.9.0 + * + * @param object $log The log to reverse. + */ + protected function auto_reverse_log( $log ) { + + wordpoints_alter_points( + $log->user_id + , -$log->points + , $log->points_type + , "reverse_{$this->log_type}" + , array( 'original_log_id' => $log->id ) + ); + + wordpoints_add_points_log_meta( $log->id, 'auto_reversed', true ); + } + + /** + * Generate the log entry for a transaction. + * + * @since 1.9.0 + * + * @param string $text The text for the log entry. + * @param int $points The number of points. + * @param string $points_type The type of points for the transaction. + * @param int $user_id The affected user's ID. + * @param string $log_type The type of transaction. + * @param array $meta Transaction meta data. + * + * @return string + */ + public function logs( $text, $points, $points_type, $user_id, $log_type, $meta ) { + + $reverse = ''; + + if ( "reverse_{$this->log_type}" === $log_type ) { + $reverse = '_reverse'; + } + + $post = false; + + if ( '' === $reverse && isset( $meta['post_id'] ) ) { + $post = get_post( $meta['post_id'] ); + } + + if ( $post ) { + + // This post exists, so we should include the permalink in the log text. + $text = $this->log_with_post_title_link( $post->ID, $reverse ); + + } else { + + // This post doesn't exist; we probably can't use the title. + $text = $this->get_option( 'log_text_no_post_title' . $reverse ); + + if ( + $this->get_option( 'log_text_post_type' . $reverse ) + && isset( $meta['post_type'] ) + && post_type_exists( $meta['post_type'] ) + ) { + + // We do know the type of post though, so include that in the log. + $text = sprintf( + $this->get_option( 'log_text_post_type' . $reverse ) + , get_post_type_object( $meta['post_type'] )->labels->singular_name + ); + + } elseif ( isset( $meta['post_title'] ) ) { + + // If the title is saved as metadata, then we can use it. + $text = sprintf( + $this->get_option( 'log_text_post_title' . $reverse ) + , $meta['post_title'] + ); + } + } + + return $text; + } + + /** + * Generate the text for a log that contains a link with the post title as text. + * + * @since 1.9.0 + * + * @param int $post_id The ID of the post being linked to. + * @param string $reverse Whether this is a reversal ('_reverse') or not (''). + * @param string $url The URL to link to. Default to the post permalink. + * + * @return string The text for the log entry. + */ + protected function log_with_post_title_link( $post_id, $reverse = '', $url = null ) { + + if ( ! isset( $url ) ) { + $url = get_permalink( $post_id ); + } + + $post_title = get_the_title( $post_id ); + + $args = array(); + + $args[] = '' + . ( $post_title ? $post_title : _x( '(no title)', 'post title', 'wordpoints' ) ) + . ''; + + $text = $this->get_option( 'log_text_post_title_and_type' . $reverse ); + + if ( + $text + && ( $post_type = get_post_field( 'post_type', $post_id ) ) + && post_type_exists( $post_type ) + ) { + $args[] = get_post_type_object( $post_type )->labels->singular_name; + } else { + $text = $this->get_option( 'log_text_post_title' . $reverse ); + } + + return vsprintf( $text, $args ); + } + /** * Generate a description for an instance of this hook. * @@ -97,6 +314,20 @@ protected function form( $instance ) { get_option( 'disable_auto_reverse_label' ); + + if ( $disable_label ) { + + ?> + +

+ /> + +

+ + name, 'comments' ); } + + /** + * Check if a user can view a particular log entry. + * + * @since 1.9.0 + * + * @WordPress/filter wordpoints_user_can_view_points_log-{$this->log_type} + * Added by the constructor. + * + * @param bool $can_view Whether the user can view this log entry. + * @param object $log The log object. + * + * @return bool Whether the user can view this log. + */ + public function user_can_view( $can_view, $log ) { + + if ( $can_view ) { + $post_id = wordpoints_get_points_log_meta( $log->id, 'post_id', true ); + + if ( $post_id ) { + $can_view = current_user_can( 'read_post', $post_id ); + } + } + + return $can_view; + } + + /** + * Clean the logs when a post is deleted. + * + * Cleans the metadata for any logs related to this post. The post ID meta field + * is updated in the database, to instead store the post type. If the post type + * isn't available, we just delete those rows. + * + * After the metadata is cleaned up, the affected logs are regenerated. + * + * @since 1.9.0 + * + * @WordPress\action delete_post Added by the constructor. + * + * @param int $post_id The ID of the post being deleted. + * + * @return void + */ + public function clean_logs_on_post_deletion( $post_id ) { + + $logs_query = new WordPoints_Points_Logs_Query( + array( + 'log_type' => $this->log_type, + 'meta_query' => array( + array( + 'key' => 'post_id', + 'value' => $post_id, + ), + ), + ) + ); + + $logs = $logs_query->get(); + + if ( ! $logs ) { + return; + } + + $post = get_post( $post_id ); + + foreach ( $logs as $log ) { + + wordpoints_delete_points_log_meta( $log->id, 'post_id' ); + + if ( $post ) { + + wordpoints_add_points_log_meta( + $log->id + , 'post_type' + , $post->post_type + ); + } + } + + wordpoints_regenerate_points_logs( $logs ); + } } // EOF diff --git a/src/components/points/includes/hooks/comment-received.php b/src/components/points/includes/hooks/comment-received.php index 03ea78a1..7e9a8369 100644 --- a/src/components/points/includes/hooks/comment-received.php +++ b/src/components/points/includes/hooks/comment-received.php @@ -43,6 +43,10 @@ public function __construct() { /* translators: %s will be the post's title. */ 'log_text_post_title_reverse' => _x( 'Comment received on %s removed.', 'points log description', 'wordpoints' ), 'log_text_no_post_title_reverse' => _x( 'Comment received removed.', 'points log description', 'wordpoints' ), + /* translators: %s is the name of a post type. */ + 'log_text_post_type' => _x( 'Received a comment on a %s.', 'points log description', 'wordpoints' ), + /* translators: %s is the name of a post type. */ + 'log_text_post_type_reverse' => _x( 'Comment received on a %s removed.', 'points log description', 'wordpoints' ), ) ); } diff --git a/src/components/points/includes/hooks/comment-removed.php b/src/components/points/includes/hooks/comment-removed.php index c80c22b7..83da47a0 100644 --- a/src/components/points/includes/hooks/comment-removed.php +++ b/src/components/points/includes/hooks/comment-removed.php @@ -7,8 +7,10 @@ * @since 1.4.0 */ -// Register the comment removed hook. -WordPoints_Points_Hooks::register( 'WordPoints_Comment_Removed_Points_Hook' ); +if ( get_site_option( 'wordpoints_comment_removed_hook_legacy' ) ) { + // Register the comment removed hook. + WordPoints_Points_Hooks::register( 'WordPoints_Comment_Removed_Points_Hook' ); +} /** * Comment removed points hook. @@ -18,6 +20,7 @@ * Prior to version 1.4.0, this functionality was part of the comment points hook. * * @since 1.4.0 + * @deprecated 1.9.0 */ class WordPoints_Comment_Removed_Points_Hook extends WordPoints_Post_Type_Points_Hook_Base { @@ -42,7 +45,7 @@ public function __construct() { , array( 'description' => __( 'Comment removed from the site.', 'wordpoints' ), /* translators: the post type name. */ - 'post_type_description' => __( 'Comment on a %s removed from the site.', 'wordpoints' ), + 'post_type_description' => __( 'Comment on a %s removed from the site.', 'wordpoints' ), 'post_type_filter' => array( $this, 'post_type_supports_comments' ), 'points_label' => __( 'Points subtracted if comment removed:', 'wordpoints' ), ) @@ -154,21 +157,14 @@ public function new_comment_hook( $comment_id, $comment ) { */ public function logs( $text, $points, $points_type, $user_id, $log_type, $meta ) { - switch ( $meta['status'] ) { - - case 'spam': - $message = _x( 'Comment marked as spam.', 'points log description', 'wordpoints' ); - break; - - case 'trash': - $message = _x( 'Comment moved to trash.', 'points log description', 'wordpoints' ); - break; - - default: - $message = _x( 'Comment unapproved.', 'points log description', 'wordpoints' ); - } - - return $message; + return wordpoints_points_logs_comment_disapprove( + $text + , $points + , $points_type + , $user_id + , $log_type + , $meta + ); } /** diff --git a/src/components/points/includes/hooks/comment.php b/src/components/points/includes/hooks/comment.php index 9a3fdba2..df86ee68 100644 --- a/src/components/points/includes/hooks/comment.php +++ b/src/components/points/includes/hooks/comment.php @@ -42,15 +42,24 @@ public function __construct() { /* translators: %s will be the post's title. */ 'log_text_post_title' => _x( 'Comment on %s.', 'points log description', 'wordpoints' ), 'log_text_no_post_title' => _x( 'Comment', 'points log description', 'wordpoints' ), + /* translators: %s is the name of a post type. */ + 'log_text_post_type' => _x( 'Comment on a %s.', 'points log description', 'wordpoints' ), + /* translators: %s will be the post's title. */ + 'log_text_post_title_reverse' => _x( 'Comment on %s removed.', 'points log description', 'wordpoints' ), + 'log_text_no_post_title_reverse' => _x( 'Comment removed.', 'points log description', 'wordpoints' ), + /* translators: %s is the name of a post type. */ + 'log_text_post_type_reverse' => _x( 'Comment on a %s removed.', 'points log description', 'wordpoints' ), 'last_status_meta_key' => 'wordpoints_last_status', ) ); - } - /** - * @since 1.8.0 - */ - public function reverse_hook( $new_status, $old_status, $comment ) {} + if ( get_site_option( 'wordpoints_comment_hook_legacy' ) ) { + $this->set_option( + 'disable_auto_reverse_label' + , __( 'Revoke the points if the comment is removed.', 'wordpoints' ) + ); + } + } /** * Generate the log entry for an approve comment transaction. @@ -79,7 +88,7 @@ public function approve_logs( $text, $points, $points_type, $user_id, $log_type, * * @since 1.0.0 * @deprecated 1.4.0 - * @deprecated Use WordPoints_Comment_Removed_Points_Hook::logs() instead. + * @deprecated Use wordpoints_points_logs_comment_disapprove() instead. * * @param string $text The text for the log entry. * @param int $points The number of points. @@ -92,15 +101,16 @@ public function approve_logs( $text, $points, $points_type, $user_id, $log_type, */ public function disapprove_logs( $text, $points, $points_type, $user_id, $log_type, $meta ) { - _deprecated_function( __METHOD__, '1.4.0', 'WordPoints_Comment_Removed_Points_Hook::logs()' ); - - $hook = WordPoints_Points_Hooks::get_handler_by_id_base( 'wordpoints_comment_removed_points_hook' ); + _deprecated_function( __METHOD__, '1.4.0', 'wordpoints_points_logs_comment_disapprove' ); - if ( $hook ) { - $text = $hook->logs( $text, $points, $points_type, $user_id, $log_type, $meta ); - } - - return $text; + return wordpoints_points_logs_comment_disapprove( + $text + , $points + , $points_type + , $user_id + , $log_type + , $meta + ); } /** diff --git a/src/components/points/includes/hooks/periodic.php b/src/components/points/includes/hooks/periodic.php index 6830241c..e223def1 100644 --- a/src/components/points/includes/hooks/periodic.php +++ b/src/components/points/includes/hooks/periodic.php @@ -85,7 +85,7 @@ public function hook() { if ( ! isset( $last_visit[ $points_type ] ) - || (int)( $last_visit[ $points_type ] / $instance['period'] ) < (int)( $now / $instance['period'] ) + || (int) ( $last_visit[ $points_type ] / $instance['period'] ) < (int) ( $now / $instance['period'] ) ) { wordpoints_add_points( diff --git a/src/components/points/includes/hooks/post-delete.php b/src/components/points/includes/hooks/post-delete.php index eb90f21c..48c2ef27 100644 --- a/src/components/points/includes/hooks/post-delete.php +++ b/src/components/points/includes/hooks/post-delete.php @@ -7,8 +7,10 @@ * @since 1.4.0 */ -// Register the post delete hook. -WordPoints_Points_Hooks::register( 'WordPoints_Post_Delete_Points_Hook' ); +if ( get_site_option( 'wordpoints_post_delete_hook_legacy' ) ) { + // Register the post delete hook. + WordPoints_Points_Hooks::register( 'WordPoints_Post_Delete_Points_Hook' ); +} /** * Post delete points hook. @@ -16,6 +18,7 @@ * Subtracts points when a post is permanently deleted. * * @since 1.4.0 + * @deprecated 1.9.0 */ class WordPoints_Post_Delete_Points_Hook extends WordPoints_Post_Type_Points_Hook_Base { @@ -107,19 +110,14 @@ public function hook( $post_id ) { */ public function logs( $text, $points, $points_type, $user_id, $log_type, $meta ) { - if ( isset( $meta['post_type'] ) ) { - - $post_type = get_post_type_object( $meta['post_type'] ); - - if ( ! is_null( $post_type ) ) { - - /* translators: 1 is the post type name, 2 is the post title. */ - return sprintf( _x( '%1$s “%2$s” deleted.', 'points log description', 'wordpoints' ), $post_type->labels->singular_name, $meta['post_title'] ); - } - } - - /* translators: %s will be the post title. */ - return sprintf( _x( 'Post “%s” deleted.', 'points log description', 'wordpoints' ), $meta['post_title'] ); + return wordpoints_points_logs_post_delete( + $text + , $points + , $points_type + , $user_id + , $log_type + , $meta + ); } /** diff --git a/src/components/points/includes/hooks/post.php b/src/components/points/includes/hooks/post.php index 57b243bf..d69008bb 100644 --- a/src/components/points/includes/hooks/post.php +++ b/src/components/points/includes/hooks/post.php @@ -16,12 +16,16 @@ * Awards points when a post is published. * * @since 1.0.0 - * @since 1.4.0 No longer subtracts points when a hook is deleted. - * - * @see WordPoints_Post_Delete_Points_Hook + * @since 1.4.0 No longer subtracts points when a post is deleted. + * @since 1.9.0 Automatically subtracts points when a post is deleted. */ class WordPoints_Post_Points_Hook extends WordPoints_Post_Type_Points_Hook_Base { + /** + * @since 1.9.0 + */ + protected $log_type = 'post_publish'; + /** * The default values. * @@ -29,7 +33,11 @@ class WordPoints_Post_Points_Hook extends WordPoints_Post_Type_Points_Hook_Base * * @type array $defaults */ - protected $defaults = array( 'points' => 20, 'post_type' => 'ALL' ); + protected $defaults = array( + 'points' => 20, + 'post_type' => 'ALL', + 'auto_reverse' => 1, + ); /** * Set up the hook. @@ -41,19 +49,42 @@ class WordPoints_Post_Points_Hook extends WordPoints_Post_Type_Points_Hook_Base */ public function __construct() { - parent::init( + parent::__construct( _x( 'Post Publish', 'points hook name', 'wordpoints' ) , array( 'description' => __( 'New post published.', 'wordpoints' ), /* translators: the post type name. */ 'post_type_description' => __( 'New %s published.', 'wordpoints' ), + /* translators: %s will be a link to the post. */ + 'log_text_post_title' => _x( 'Post %s published.', 'points log description', 'wordpoints' ), + /* translators: 1 is the post type name, 2 is a link to the post. */ + 'log_text_post_title_and_type' => _x( '%2$s %1$s published.', 'points log description', 'wordpoints' ), + /* translators: %s is the name of a post type. */ + 'log_text_post_type' => _x( '%s published.', 'points log description', 'wordpoints' ), + 'log_text_no_post_title' => _x( 'Post published.', 'points log description', 'wordpoints' ), + /* translators: %s is the name of a post type. */ + 'log_text_post_type_reverse' => _x( '%s deleted.', 'points log description', 'wordpoints' ), + /* translators: 1 is the post type name, 2 is the post title. */ + 'log_text_post_title_and_type_reverse' => _x( '%1$s “%2$s” deleted.', 'points log description', 'wordpoints' ), + /* translators: %s will be the post title. */ + 'log_text_post_title_reverse' => _x( 'Post “%s” deleted.', 'points log description', 'wordpoints' ), + 'log_text_no_post_title_reverse' => _x( 'Post deleted.', 'points log description', 'wordpoints' ), ) ); + if ( get_site_option( 'wordpoints_post_hook_legacy' ) ) { + $this->set_option( + 'disable_auto_reverse_label' + , __( 'Revoke the points if the post is permanently deleted.', 'wordpoints' ) + ); + } + add_action( 'transition_post_status', array( $this, 'publish_hook' ), 10, 3 ); + add_action( 'delete_post', array( $this, 'reverse_hook' ) ); + + // Back-compat. + remove_filter( "wordpoints_points_log-{$this->log_type}", array( $this, 'logs' ), 10, 6 ); add_filter( 'wordpoints_points_log-post_publish', array( $this, 'publish_logs' ), 10, 6 ); - add_action( 'delete_post', array( $this, 'clean_logs_on_post_deletion' ) ); - add_filter( 'wordpoints_user_can_view_points_log-post_publish', array( $this, 'user_can_view' ), 10, 2 ); } /** @@ -106,6 +137,32 @@ public function publish_hook( $new_status, $old_status, $post ) { } } + /** + * Automatically reverse any transactions for a post when it is deleted. + * + * @since 1.9.0 + * + * @WordPress\action delete_post Added by the constructor. + */ + public function reverse_hook( $post_id ) { + + $post_type = get_post_field( 'post_type', $post_id ); + if ( ! $this->should_do_auto_reversals_for_post_type( $post_type ) ) { + return; + } + + $logs = $this->get_logs_to_auto_reverse( + array( + 'key' => 'post_id', + 'value' => $post_id, + ) + ); + + foreach ( $logs as $log ) { + $this->auto_reverse_log( $log ); + } + } + /** * Remove points when a post is deleted. * @@ -113,13 +170,13 @@ public function publish_hook( $new_status, $old_status, $post ) { * @since 1.1.0 The post_type is now passed as metadata when points are awarded. * @since 1.1.2 Points are only removed if the post type is public. * @deprecated 1.4.0 - * @deprecated Use the WordPoints_Post_Delete_Points_Hook instead. + * @deprecated Use WordPoints_Post_Points_Hook::reverse_hook() instead. * * @param int $post_id The post's ID. */ public function delete_hook( $post_id ) { - _deprecated_function( __METHOD__, '1.4.0', 'WordPoints_Post_Delete_Points_Hook::hook()' ); + _deprecated_function( __METHOD__, '1.4.0', 'WordPoints_Post_Points_Hook::reverse_hook()' ); } /** @@ -140,52 +197,15 @@ public function delete_hook( $post_id ) { */ public function publish_logs( $text, $points, $points_type, $user_id, $log_type, $meta ) { - $post = null; - - if ( isset( $meta['post_id'] ) ) { - $post = get_post( $meta['post_id'], OBJECT, 'display' ); - } - - if ( ! $post ) { - - $post_type = null; - - if ( isset( $meta['post_type'] ) && post_type_exists( $meta['post_type'] ) ) { - $post_type = get_post_type_object( $meta['post_type'] ); - } - - if ( $post_type ) { - /* translators: %s is the name of a post type. */ - return sprintf( _x( '%s published.', 'points log description', 'wordpoints' ), $post_type->labels->singular_name ); - } else { - return _x( 'Post published.', 'points log description', 'wordpoints' ); - } - - } else { - - $link = '' . ( $post->post_title ? $post->post_title : _x( '(no title)', 'post title', 'wordpoints' ) ) . ''; - - $post_type = get_post_type_object( $post->post_type ); - - if ( is_null( $post_type ) ) { - - /* translators: %s will be a link to the post. */ - return sprintf( _x( 'Post %s published.', 'points log description', 'wordpoints' ), $link ); - } - - /* translators: 1 is the post type name, 2 is a link to the post. */ - return sprintf( _x( '%1$s %2$s published.', 'points log description', 'wordpoints' ), $post_type->labels->singular_name, $link ); - } + return parent::logs( $text, $points, $points_type, $user_id, $log_type, $meta ); } /** * Generate the log entry for a transaction. * - * The data isn't sanitized here becuase we do that before saving it. - * * @since 1.0.0 * @deprecated 1.4.0 - * @deprecated Use WordPoints_Post_Delete_Points_Hook::logs() instead. + * @deprecated Use wordpoints_points_logs_post_delete() instead. * * @param string $text The text for the log entry. * @param int $points The number of points. @@ -198,15 +218,16 @@ public function publish_logs( $text, $points, $points_type, $user_id, $log_type, */ public function delete_logs( $text, $points, $points_type, $user_id, $log_type, $meta ) { - _deprecated_function( __METHOD__, '1.4.0', 'WordPoints_Post_Delete_Points_Hook::logs()' ); - - $hook = WordPoints_Points_Hooks::get_handler_by_id_base( 'wordpoints_post_delete_points_hook' ); + _deprecated_function( __METHOD__, '1.4.0', 'wordpoints_points_logs_post_delete' ); - if ( $hook ) { - $text = $hook->logs( $text, $points, $points_type, $user_id, $log_type, $meta ); - } - - return $text; + return wordpoints_points_logs_post_delete( + $text + , $points + , $points_type + , $user_id + , $log_type + , $meta + ); } /** @@ -234,89 +255,6 @@ public function awarded_points_already( $post_id, $points_type ) { return ( $query->count() > 0 ); } - /** - * Clean the logs when a post is deleted. - * - * Cleans the metadata for any logs related to this post. The post ID meta field - * is updated in the database, to instead store the post type. If the post type - * isn't available, we just delete those rows. - * - * After the metadata is cleaned up, the affected logs are regenerated. - * - * @since 1.2.0 - * - * @action delete_post Added by the constructor. - * - * @param int $post_id The ID of the post being deleted. - * - * @return void - */ - public function clean_logs_on_post_deletion( $post_id ) { - - global $wpdb; - - $logs_query = new WordPoints_Points_Logs_Query( - array( - 'log_type' => 'post_publish', - 'meta_query' => array( - array( - 'key' => 'post_id', - 'value' => $post_id, - ), - ), - ) - ); - - $logs = $logs_query->get(); - - if ( ! $logs ) { - return; - } - - $post = get_post( $post_id ); - - foreach ( $logs as $log ) { - - wordpoints_delete_points_log_meta( $log->id, 'post_id' ); - - if ( $post ) { - - wordpoints_add_points_log_meta( - $log->id - , 'post_type' - , $post->post_type - ); - } - } - - wordpoints_regenerate_points_logs( $logs ); - } - - /** - * Check if a user can view a particular log entry. - * - * @since 1.3.0 - * - * @filter wordpoints_user_can_view_points_log-post_publish Added by the constructor. - * - * @param bool $can_view Whether the user can view this log entry. - * @param object $log The log object. - * - * @return bool Whether the user can view this log. - */ - public function user_can_view( $can_view, $log ) { - - if ( $can_view ) { - $post_id = wordpoints_get_points_log_meta( $log->id, 'post_id', true ); - - if ( $post_id ) { - $can_view = current_user_can( 'read_post', $post_id ); - } - } - - return $can_view; - } - } // class WordPoints_Post_Points_Hook // EOF diff --git a/src/components/points/includes/logs.php b/src/components/points/includes/logs.php index 9f252514..52275da0 100644 --- a/src/components/points/includes/logs.php +++ b/src/components/points/includes/logs.php @@ -186,11 +186,10 @@ public static function get_query_data( $query_slug, $data = null ) { * Queries cannot be deregistered at present. Use the * 'wordpoints_points_logs_query_args' filter instead. * - * To have your query cached, pass a list of the query methods that should be cached - * via $data['cache_queries']. You can specify the cache key to use in - * $data['cache_key'], though this is optional, and '$slug:%points_type%' will by - * used by default. For more information on logs query caching, see - * WordPoints_Points_Logs_Query::prime_cache(). + * To have your query cached, set $data['cache_queries'] to true. You can specify + * the cache key to use in $data['cache_key'], though this is optional, and + * '$slug:%points_type%' will by used by default. For more information on logs + * query caching, see WordPoints_Points_Logs_Query::prime_cache(). * * @since 1.0.0 * @since 1.5.0 The $data parameter was added. @@ -205,7 +204,7 @@ public static function get_query_data( $query_slug, $data = null ) { * Other data for this query. * * @type string $cache_key Cache key format. - * @type string|array $cache_queries Type(s) of queries to cache. + * @type string|array $cache_queries Whether to cache this query. * @type bool $network_wide Whether this is a network-wide query. * } * @@ -314,7 +313,7 @@ function wordpoints_get_points_logs_query( $points_type, $query_slug = 'default' $query->prime_cache( $query_data['cache_key'] - , $query_data['cache_queries'] + , null , $query_data['network_wide'] ); } @@ -456,6 +455,17 @@ function wordpoints_show_points_logs( $logs_query, array $args = array() ) { 'time' => _x( 'Time', 'points logs table heading', 'wordpoints' ), ); + $points_type = $logs_query->get_arg( 'points_type' ); + + if ( ! empty( $points_type ) ) { + + $points_type_name = wordpoints_get_points_type_setting( $points_type, 'name' ); + + if ( ! empty( $points_type_name ) ) { + $columns['points'] = $points_type_name; + } + } + ?>
@@ -466,7 +476,7 @@ function wordpoints_show_points_logs( $logs_query, array $args = array() ) {