Compare commits

..

No commits in common. "stable" and "v1.1.1" have entirely different histories.

39 changed files with 8960 additions and 1344 deletions

View File

@ -1,17 +1,13 @@
/.git /.git
/.github /.github
/assets /assets
/bin
/dist
/node_modules /node_modules
/tests
/vendor /vendor
/.distignore /.distignore
/.drone.yml /.drone.yml
/.eslintrc.json /.eslintrc.json
/.gitattributes /.gitattributes
/.gitignore /.gitignore
/.phpunit.result.cache
/.stylelintrc.json /.stylelintrc.json
/.travis.yml /.travis.yml
/composer.json /composer.json
@ -21,3 +17,4 @@
/package-lock.json /package-lock.json
/phpcs.xml /phpcs.xml
/phpunit.xml /phpunit.xml
/RoboFile.php

View File

@ -6,19 +6,19 @@ steps:
- name: composer-install - name: composer-install
image: composer:2 image: composer:2
commands: commands:
- composer install --ignore-platform-req=php - composer install
- name: lint-php - name: lint-php
image: php:8.2 image: composer:2
commands: commands:
- ./vendor/bin/phpcs - composer lint-php
depends_on: depends_on:
- composer-install - composer-install
- name: node-install - name: node-install
image: node:22 image: node:14
commands: commands:
- npm install - npm ci
- name: lint-assets - name: lint-assets
image: node:22 image: node:14
commands: commands:
- npx eslint scripts/block.js - npx eslint scripts/block.js
- npx eslint scripts/liveticker.js - npx eslint scripts/liveticker.js

View File

@ -4,7 +4,7 @@
"browser": true "browser": true
}, },
"globals": { "globals": {
"scliveticker": "readonly", "sclivetickerAjax": "readonly",
"wp": "readonly" "wp": "readonly"
}, },
"extends": [ "extends": [

34
.gitattributes vendored
View File

@ -1,20 +1,16 @@
/assets export-ignore /assets export-ignore
/.github export-ignore .distignore export-ignore
/.distignore export-ignore .drone.yml export-ignore
/.drone.yml export-ignore .eslintrc.json export-ignore
/.eslintrc.json export-ignore .gitattributes export-ignore
/.gitattributes export-ignore .gitignore export-ignore
/.gitignore export-ignore .stylelintrc.json export-ignore
/.stylelintrc.json export-ignore .travis.yml export-ignore
/.travis.yml export-ignore composer.json export-ignore
/bin export-ignore composer.lock export-ignore
/composer.json export-ignore CONTRIBUTING.md export-ignore
/composer.lock export-ignore package.json export-ignore
/CONTRIBUTING.md export-ignore package-lock.json export-ignore
/dist export-ignore phpcs.xml export-ignore
/package.json export-ignore phpunit.xml export-ignore
/package-lock.json export-ignore RoboFile.php export-ignore
/phpcs.xml export-ignore
/phpunit.xml export-ignore
/tests export-ignore

View File

@ -1,85 +0,0 @@
name: CI
on: [push, pull_request]
jobs:
integration:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- php: '8.3'
wordpress: '6.6'
- php: '8.2'
wordpress: '6.4'
- php: '8.1'
wordpress: '6.2'
- php: '8.0'
wordpress: '6.0'
- php: '7.4'
wordpress: '5.9'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install SVN
run: |
if ! command -v svn > /dev/null; then
sudo apt-get update
sudo apt-get install -y subversion
fi
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug
tools: composer
- name: Setup DB
run: sudo sudo systemctl start mysql.service
- name: Setup WP
run: bash bin/install-wp-tests.sh wordpress root root localhost "${{ matrix.wordpress }}"
- name: Install
run: composer install --no-interaction
- name: Test
run: composer test
quality:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
tools: composer
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install
run: |
composer install --no-interaction
npm install
- name: Code style checks for PHP, JS and CSS
run: |
composer lint-php
composer lint-js
composer lint-css
analysis:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Analyze with SonarCloud
if: env.SONAR_TOKEN != ''
uses: sonarsource/sonarcloud-github-action@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
args: >
-Dsonar.organization=stklcode-github
-Dsonar.projectKey=stklcode:wp-liveticker
-Dsonar.sources=includes,scripts,views,stklcode-liveticker.php
-Dsonar.exclusions=scripts/*.min.js,styles/*.min.css

View File

@ -2,27 +2,19 @@ name: Plugin asset/readme update
on: on:
push: push:
branches: branches:
- stable - master
jobs: jobs:
master: master:
name: Push to stable name: Push to master
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - uses: actions/checkout@v2
uses: actions/checkout@v4 - uses: php-actions/composer@v5
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
tools: composer
- name: Install
run: composer install --no-interaction
- name: Clean README.md - name: Clean README.md
run: tail -n +7 README.md > README.md.tmp && mv README.md.tmp README.md run: tail -n +7 README.md > README.md.tmp && mv README.md.tmp README.md
- name: WordPress.org plugin asset/readme update - name: WordPress.org plugin asset/readme update
uses: 10up/action-wordpress-plugin-asset-update@stable uses: 10up/action-wordpress-plugin-asset-update@stable
env: env:
SLUG: stklcode-liveticker
ASSETS_DIR: assets ASSETS_DIR: assets
README_NAME: README.md README_NAME: README.md
SVN_PASSWORD: ${{ secrets.WP_SVN_PASSWORD }} SVN_PASSWORD: ${{ secrets.WP_SVN_PASSWORD }}

View File

@ -1,23 +0,0 @@
name: Plugin check
on:
push:
branches: [ 'stable', 'release/*' ]
pull_request:
branches: [ 'stable' ]
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Package plugin
run: |
mkdir -p ./dist
tail -n +7 README.md > README.md.tmp && mv README.md.tmp README.md
rsync -rc --exclude-from=.distignore ./ ./dist/stklcode-liveticker --delete --delete-excluded
- name: Check WP plugin
uses: wordpress/plugin-check-action@v1
with:
build-dir: ./dist/stklcode-liveticker

View File

@ -9,21 +9,13 @@ jobs:
name: New tag name: New tag
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - uses: actions/checkout@v2
uses: actions/checkout@v4 - uses: php-actions/composer@v5
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
tools: composer
- name: Install
run: composer install --no-interaction
- name: Clean README.md - name: Clean README.md
run: tail -n +7 README.md > README.md.tmp && mv README.md.tmp README.md run: tail -n +7 README.md > README.md.tmp && mv README.md.tmp README.md
- name: WordPress Plugin Deploy - name: WordPress Plugin Deploy
uses: 10up/action-wordpress-plugin-deploy@stable uses: 10up/action-wordpress-plugin-deploy@stable
env: env:
SLUG: stklcode-liveticker
ASSETS_DIR: assets ASSETS_DIR: assets
SVN_PASSWORD: ${{ secrets.WP_SVN_PASSWORD }} SVN_PASSWORD: ${{ secrets.WP_SVN_PASSWORD }}
SVN_USERNAME: ${{ secrets.WP_SVN_USERNAME }} SVN_USERNAME: ${{ secrets.WP_SVN_USERNAME }}

5
.gitignore vendored
View File

@ -1,8 +1,7 @@
/vendor/ /vendor/
/node_modules/ /node_modules/
/dist/ /dist/
/.phpunit.result.cache .idea
/composer.lock .phpunit.result.cache
/package-lock.json
**/*.min.css **/*.min.css
**/*.min.js **/*.min.js

27
.travis.yml Normal file
View File

@ -0,0 +1,27 @@
language: php
php:
- '7.3'
- '7.4'
- '8.0'
- nightly
dist: bionic
matrix:
allow_failures:
- php: nightly
before_install:
- nvm install 14
before_script:
- composer install
- npm install
script:
- composer lint-php
- composer lint-js
- composer lint-css
notifications:
slack:
secure: "R40BhRCETuDule7lz4oGN+qyLvd7dBmuEu6hVELNhWg3DgCgYOXyrWR2dgxsWsAZ3sldpWGfTJKzSShdDanGCpygpYzuvXxjt23YYJ2ihrohYJwiGIhkR9c24LF2yvWBQDBNZaeLBQ3o6FSnbkTBsmRy5ShgKehfKCOQTKmI1yWHi3fvkMElTorrJc710O41yy/bRKBnoIYd4ZfpLMSSVGCPzR5lZPZy3EiGWXPgYdY7jGMI7ADsy+T5VWHyFqgSSJz/U2bcryKzF08FAry8pyu9lN3r61kXHfVCCJX+kcsFxW9yCfuPLnLu14O776y3U6zrX9is+8mEfkMuTXFaL5o8+iq32AmFjTIDQn6o9BKHsknfmppjwZiLgFTp1T7Z/XR6I4nyK9Z5HXDU2HS0eCUknbgXlMLhxWpKhkyx4rQELuvVlgD+u7yRYraawc3v1ycqaPj0S0G5QBFljSuxsZgNnX1hs8VmgafIvOq5qm4ZVVBhhbz+LgvW1m9COr8DDPVhWWdpcWzF8jtkqC3m4Q/1Ssc6T/MbJMgcXRq/C4DlfEs4aYGYfSl7gLtF2PwlEQCppKJwx0fEPkcbZZ1PjpzF+JMwwRmWS88R0oRyThOyCwlG50c+ktB94pJC+sP1aQZrLAd4WDKUPD9vJTas86V3XBjTUJPs8HQaBDFqFdg="

View File

@ -1,19 +1,19 @@
[![CI](https://github.com/stklcode/wp-liveticker/actions/workflows/test.yml/badge.svg?branch=stable)](https://github.com/stklcode/wp-liveticker/actions/workflows/test.yml) [![Build Status](https://travis-ci.com/stklcode/wp-liveticker.svg?branch=master)](https://travis-ci.com/stklcode/wp-liveticker)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=stklcode%3Awp-liveticker&metric=alert_status)](https://sonarcloud.io/dashboard?id=stklcode%3Awp-liveticker) [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=de.stklcode.web.wordpress.plugins%3Awp-liveticker&metric=alert_status)](https://sonarcloud.io/dashboard?id=de.stklcode.web.wordpress.plugins%3Awp-liveticker)
[![WP Plugin Version](https://img.shields.io/wordpress/plugin/v/stklcode-liveticker.svg)](https://wordpress.org/plugins/stklcode-liveticker/) [![WP Plugin Version](https://img.shields.io/wordpress/plugin/v/stklcode-liveticker.svg)](https://wordpress.org/plugins/stklcode-liveticker/)
[![Packagist Version](https://img.shields.io/packagist/v/stklcode/stklcode-liveticker.svg)](https://packagist.org/packages/stklcode/stklcode-liveticker) [![Packagist Version](https://img.shields.io/packagist/v/stklcode/stklcode-liveticker.svg)](https://packagist.org/packages/stklcode/stklcode-liveticker)
[![License](https://img.shields.io/badge/license-GPL%20v2-blue.svg)](https://github.com/stklcode/wp-liveticker/blob/stable/LICENSE.md) [![License](https://img.shields.io/badge/license-GPL%20v2-blue.svg)](https://github.com/stklcode/wp-liveticker/blob/master/LICENSE.md)
# Liveticker (by stklcode) # Liveticker (by stklcode)
* Contributors: stklcode * Contributors: Stefan Kalscheuer
* Tags: liveticker, feed, rss * Tags: liveticker, feed, rss
* Requires at least: 5.0 * Requires at least: 4.0
* Tested up to: 6.7 * Tested up to: 5.7
* Requires PHP: 7.2 * Requires PHP: 5.6
* Stable tag: 1.3.0 * Stable tag: 1.1.1
* License: GPLv2 or later * License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html * License URI: http://www.gnu.org/licenses/gpl-2.0.html
A simple ajaxified liveticker plugin for WordPress. A simple ajaxified liveticker plugin for WordPress.
@ -42,8 +42,8 @@ Alternatively you can also use _Copmposer_.
### Requirements ### ### Requirements ###
* PHP 7.2 or above * PHP 5.6 or above
* WordPress 5.0 or above * WordPress 4.0 or above
## Frequently asked questions ## Frequently asked questions
@ -80,34 +80,6 @@ caching time of 12 hours obviously makes no sense.
## Changelog ## Changelog
### 1.3.0 - 2025-03-10
* Requires at least PHP 7.2 and WordPress 5.0
* Sorting direction can now be changed for liveticker blocks
### 1.2.3 - 2025-02-04
* Escape ticker ID in shortcode output
* Tested with WP 6.7
### 1.2.2 - 2024-03-02
* Update use of deprecated WP core functions
* Extend output sanitization
* Tested with WP 6.4
### 1.2.1 - 2022-02-01
* Fix issue with limit in Gutenberg block.
### 1.2.0 - 2022-01-23
* Requires WordPress 4.7 or above
* Migrated AJAX to REST API
* Resolved Javascript compatibility issues with IE11
* Added optional shortcode support for tick content
* Support embedded JavaScript execution in tick content (e.g. for social media integrations)
### 1.1.1 - 2021-03-20 ### 1.1.1 - 2021-03-20
* "Ticker" taxonomy name is now translatable * "Ticker" taxonomy name is now translatable

527
RoboFile.php Normal file
View File

@ -0,0 +1,527 @@
<?php
/**
* Liveticker Robo build script.
*
* This file contains the Robo tasks for building a distributable plugin package.
* Should not be included in final package.
*
* @author Stefan Kalscheuer <stefan@stklcode.de>
*
* @package Liveticker
* @version 1.0.0
*/
use Robo\Exception\TaskException;
use Robo\Tasks;
use Symfony\Component\Finder\Finder;
/**
* Class RoboFile
*/
class RoboFile extends Tasks {
const PROJECT_NAME = 'stklcode-liveticker';
const SVN_URL = 'https://plugins.svn.wordpress.org/stklcode-liveticker';
const OPT_TARGET = 'target';
const OPT_SKIPTEST = 'skipTests';
const OPT_SKIPSTYLE = 'skipStyle';
const OPT_MINIFY = 'minify';
const OPT_NODE = 'node';
/**
* Version tag (read from composer.json).
*
* @var string
*/
private $version;
/**
* Target directory path.
*
* @var string
*/
private $target_dir;
/**
* Final package name.
*
* @var string
*/
private $final_name;
/**
* RoboFile constructor
*
* @param array $opts Options.
*
* @return void
*/
public function __construct( $opts = array( self::OPT_TARGET => 'dist' ) ) {
// Read composer configuration and extract version number..
$composer = json_decode( file_get_contents( __DIR__ . '/composer.json' ) );
// Extract parameter from options.
$this->version = $composer->version;
$this->target_dir = $opts[ self::OPT_TARGET ];
$this->final_name = self::PROJECT_NAME . '.' . $this->version;
}
/**
* Clean up target directory
*
* @param array $opts Options.
*
* @return void
*/
public function clean( $opts = array( self::OPT_TARGET => 'dist' ) ) {
$this->say( 'Cleaning target directory...' );
if ( is_dir( $this->target_dir . '/' . $this->final_name ) ) {
$this->_deleteDir( array( $this->target_dir . '/' . $this->final_name ) );
}
if ( is_file( $this->target_dir . '/' . $this->final_name . '.zip' ) ) {
$this->_remove( $this->target_dir . '/' . $this->final_name . '.zip' );
}
}
/**
* Run PHPUnit tests
*
* @return void
*/
public function test() {
$this->say( 'Executing PHPUnit tests...' );
$this->taskPhpUnit()->configFile( __DIR__ . '/phpunit.xml' )->run();
}
/**
* Run code style tests
*
* @param array $opts Options.
*
* @return void
*/
public function testCS(
$opts = array(
self::OPT_TARGET => 'dist',
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
$this->say( 'Executing PHPCS...' );
$this->_exec( __DIR__ . '/vendor/bin/phpcs --standard=phpcs.xml -s' );
if ( $opts[ self::OPT_NODE ] ) {
$this->say( 'Executing ESLint...' );
$this->_exec( __DIR__ . '/node_modules/eslint/bin/eslint.js ' . __DIR__ . '/scripts/block.js' );
$this->_exec( __DIR__ . '/node_modules/eslint/bin/eslint.js ' . __DIR__ . '/scripts/liveticker.js' );
$this->say( 'Executing StyleLint...' );
$this->_exec( __DIR__ . '/node_modules/stylelint/bin/stylelint.js ' . __DIR__ . '/styles/block.css' );
$this->_exec( __DIR__ . '/node_modules/stylelint/bin/stylelint.js ' . __DIR__ . '/styles/liveticker.css' );
}
}
/**
* Build a distributable bundle.
*
* @param array $opts Options.
*
* @return void
*/
public function build(
$opts = array(
self::OPT_TARGET => 'dist',
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
$this->clean( $opts );
if ( isset( $opts[ self::OPT_SKIPTEST ] ) && true === $opts[ self::OPT_SKIPTEST ] ) {
$this->say( 'Tests skipped' );
} else {
$this->test();
}
if ( isset( $opts[ self::OPT_SKIPSTYLE ] ) && true === $opts[ self::OPT_SKIPSTYLE ] ) {
$this->say( 'Style checks skipped' );
} else {
$this->testCS( $opts );
}
$this->bundle();
}
/**
* Bundle global resources.
*
* @return void
*/
private function bundle() {
$this->say( 'Bundling resources...' );
$this->taskCopyDir(
array(
'includes' => $this->target_dir . '/' . $this->final_name . '/includes',
'scripts' => $this->target_dir . '/' . $this->final_name . '/scripts',
'styles' => $this->target_dir . '/' . $this->final_name . '/styles',
'views' => $this->target_dir . '/' . $this->final_name . '/views',
)
)->run();
$this->_copy( 'stklcode-liveticker.php', $this->target_dir . '/' . $this->final_name . '/stklcode-liveticker.php' );
$this->_copy( 'README.md', $this->target_dir . '/' . $this->final_name . '/README.md' );
$this->_copy( 'LICENSE.md', $this->target_dir . '/' . $this->final_name . '/LICENSE.md' );
// Remove content before title (e.g. badges) from README file.
$this->taskReplaceInFile( $this->target_dir . '/' . $this->final_name . '/README.md' )
->regex( '/^[^\\#]*/' )
->to( '' )
->run();
}
/**
* Minify JavaScript and CSS assets in target director.
*
* @param array $opts Options.
*
* @return void
*/
public function minify(
$opts = array(
self::OPT_TARGET => 'dist',
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
)
) {
if ( $opts[ self::OPT_MINIFY ] ) {
$this->minifyJS( $opts );
$this->minifyCSS( $opts );
} else {
$this->say( 'Minification skipped.' );
}
}
/**
* Minify CSS assets.
*
* @param array $opts Options.
*
* @return void
*/
public function minifyCSS(
$opts = array(
self::OPT_TARGET => 'dist',
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
if ( ! isset( $opts[ self::OPT_MINIFY ] ) ) {
$this->say( 'CSS minification skipped.' );
return;
}
$this->say( 'Minifying CSS...' );
$finder = Finder::create()->name( '*.css*' )
->notName( '*.min.css' )
->in( $this->target_dir . '/' . $this->final_name . '/styles' );
foreach ( $finder as $file ) {
$this->taskMinify( $file )->run();
// Replace original file for in-place minification.
$abspath = $file->getPath() . '/' . $file->getFilename();
$this->_rename( str_replace( '.css', '.min.css', $abspath ), $abspath, true );
}
}
/**
* Minify JavaScript assets.
*
* @param array $opts Options.
*
* @return void
*/
public function minifyJS(
$opts = array(
self::OPT_TARGET => 'dist',
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
if ( ! isset( $opts[ self::OPT_MINIFY ] ) ) {
$this->say( 'JS minification skipped.' );
return;
}
$this->say( 'Minifying JavaScript...' );
// Minify global JavaScripts files except already minified.
$finder = Finder::create()->name( '*.js*' )
->notName( '*.min.js' )
->in( $this->target_dir . '/' . $this->final_name . '/scripts' );
foreach ( $finder as $file ) {
$this->taskMinify( $file )->run();
// Replace original file for in-place minification.
$abspath = $file->getPath() . '/' . $file->getFilename();
$this->_rename( str_replace( '.js', '.min.js', $abspath ), $abspath, true );
}
}
/**
* Create ZIP package from distribution bundle.
*
* @param array $opts Options.
*
* @return void
*/
public function package(
$opts = array(
self::OPT_TARGET => 'dist',
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
$this->build( $opts );
$this->say( 'Packaging...' );
$this->taskPack( $this->target_dir . '/' . $this->final_name . '.zip' )
->addDir( '', $this->target_dir . '/' . $this->final_name )
->run();
}
/**
* Deploy development version (trunk).
*
* @param array $opts Options.
*
* @return void
* @throws TaskException On errors.
*/
public function deployTrunk(
$opts = array(
self::OPT_TARGET => 'dist',
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
// First execute build job.
$this->build( $opts );
// Prepare VCS, either checkout or update local copy.
$this->prepareVCS();
$this->say( 'Preparing deployment directory...' );
$this->updateVCStrunk();
// Update remote repository.
$this->say( 'Deploying...' );
$this->commitVCS(
'--force trunk/*',
'Updated ' . self::PROJECT_NAME . ' trunk'
);
}
/**
* Deploy current version tag.
*
* @param array $opts Options.
*
* @return void
* @throws TaskException On errors.
*/
public function deployTag(
$opts = array(
self::OPT_TARGET => 'dist',
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
// First execute build job.
$this->build( $opts );
// Prepare VCS, either checkout or update local copy.
$this->prepareVCS();
$this->say( 'Preparing deployment directory...' );
$this->updateVCStag();
// Update remote repository.
$this->say( 'Deploying...' );
$this->commitVCS(
'tags/' . $this->version,
'Updated ' . self::PROJECT_NAME . ' v' . $this->version
);
}
/**
* Deploy current version tag.
*
* @param array $opts Options.
*
* @return void
* @throws TaskException On errors.
*/
public function deployReadme(
$opts = array(
self::OPT_TARGET => 'dist',
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
// First execute build job.
$this->build( $opts );
// Prepare VCS, either checkout or update local copy.
$this->prepareVCS();
$this->updateVCSreadme();
// Update remote repository.
$this->say( 'Deploying...' );
$this->commitVCS(
'--force trunk/README.md',
'Updated ' . self::PROJECT_NAME . ' ReadMe'
);
}
/**
* Deploy current version tag and trunk.
*
* @param array $opts Options.
*
* @return void
* @throws TaskException On errors.
*/
public function deployAll(
$opts = array(
self::OPT_TARGET => 'dist',
self::OPT_SKIPTEST => false,
self::OPT_SKIPSTYLE => false,
self::OPT_MINIFY => true,
self::OPT_NODE => false,
)
) {
// First execute build job.
$this->build( $opts );
// Prepare VCS, either checkout or update local copy.
$this->prepareVCS();
$this->say( 'Preparing deployment directory...' );
$this->updateVCStrunk();
$this->updateVCStag();
// Update remote repository.
$this->say( 'Deploying...' );
$this->commitVCS(
array(
'--force trunk/*',
'--force tags/' . $this->version,
),
'Updated ' . self::PROJECT_NAME . ' v' . $this->version
);
}
/**
* Prepare VCS direcory.
*
* Checkout or update local copy of SVN repository.
*
* @return void
* @throws TaskException On errors.
*/
private function prepareVCS() {
if ( is_dir( $this->target_dir . '/svn' ) ) {
$this->taskSvnStack()
->stopOnFail()
->dir( $this->target_dir . '/svn/stklcode-liveticker' )
->update()
->run();
} else {
$this->_mkdir( $this->target_dir . '/svn' );
$this->taskSvnStack()
->dir( $this->target_dir . '/svn' )
->checkout( self::SVN_URL )
->run();
}
}
/**
* Commit VCS changes
*
* @param string|array $to_add Files to add.
* @param string $msg Commit message.
*
* @return void
* @throws TaskException On errors.
*/
private function commitVCS( $to_add, $msg ) {
$task = $this->taskSvnStack()
->stopOnFail()
->dir( $this->target_dir . '/svn/stklode-liveticker' );
if ( is_array( $to_add ) ) {
foreach ( $to_add as $ta ) {
$task = $task->add( $ta );
}
} else {
$task = $task->add( $to_add );
}
$task->commit( $msg )->run();
}
/**
* Update SVN readme file.
*
* @return void
*/
private function updateVCSreadme() {
$trunk_dir = $this->target_dir . '/svn/stklcode-liveticker/trunk';
$this->_copy( $this->target_dir . '/' . $this->final_name . 'README.md', $trunk_dir . 'README.md' );
}
/**
* Update SVN development version (trunk).
*
* @return void
*/
private function updateVCStrunk() {
// Clean trunk directory.
$trunk_dir = $this->target_dir . '/svn/stklcode-liveticker/trunk';
$this->taskCleanDir( $trunk_dir )->run();
// Copy built bundle to trunk.
$this->taskCopyDir( array( $this->target_dir . '/' . $this->final_name => $trunk_dir ) )->run();
}
/**
* Update current SVN version tag.
*
* @return void
*/
private function updateVCStag() {
// Clean tag directory if it exists.
$tag_dir = $this->target_dir . '/svn/stklcode-liveticker/tags/' . $this->version;
if ( is_dir( $tag_dir ) ) {
$this->taskCleanDir( $this->target_dir . '/svn/stklcode-liveticker/tags/' . $this->version )->run();
} else {
$this->_mkdir( $tag_dir );
}
// Copy built bundle to trunk.
$this->taskCopyDir( array( $this->target_dir . '/' . $this->final_name => $tag_dir ) )->run();
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -1,194 +0,0 @@
#!/usr/bin/env bash
if [ $# -lt 3 ]; then
echo "usage: $0 <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]"
exit 1
fi
DB_NAME=$1
DB_USER=$2
DB_PASS=$3
DB_HOST=${4-localhost}
WP_VERSION=${5-latest}
SKIP_DB_CREATE=${6-false}
TMPDIR=${TMPDIR-/tmp}
TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//")
WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib}
WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress}
download() {
if [ `which curl` ]; then
curl -s "$1" > "$2";
elif [ `which wget` ]; then
wget -nv -O "$2" "$1"
else
echo "Error: Neither curl nor wget is installed."
exit 1
fi
}
# Check if svn is installed
check_svn_installed() {
if ! command -v svn > /dev/null; then
echo "Error: svn is not installed. Please install svn and try again."
exit 1
fi
}
if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then
WP_BRANCH=${WP_VERSION%\-*}
WP_TESTS_TAG="branches/$WP_BRANCH"
elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
WP_TESTS_TAG="branches/$WP_VERSION"
elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
# version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
WP_TESTS_TAG="tags/${WP_VERSION%??}"
else
WP_TESTS_TAG="tags/$WP_VERSION"
fi
elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
WP_TESTS_TAG="trunk"
else
# http serves a single offer, whereas https serves multiple. we only want one
download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json
LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
if [[ -z "$LATEST_VERSION" ]]; then
echo "Latest WordPress version could not be found"
exit 1
fi
WP_TESTS_TAG="tags/$LATEST_VERSION"
fi
set -ex
install_wp() {
if [ -d $WP_CORE_DIR ]; then
return;
fi
mkdir -p $WP_CORE_DIR
if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
mkdir -p $TMPDIR/wordpress-trunk
rm -rf $TMPDIR/wordpress-trunk/*
check_svn_installed
svn export --quiet https://core.svn.wordpress.org/trunk $TMPDIR/wordpress-trunk/wordpress
mv $TMPDIR/wordpress-trunk/wordpress/* $WP_CORE_DIR
else
if [ $WP_VERSION == 'latest' ]; then
local ARCHIVE_NAME='latest'
elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then
# https serves multiple offers, whereas http serves single.
download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
# version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
LATEST_VERSION=${WP_VERSION%??}
else
# otherwise, scan the releases and get the most up to date minor version of the major release
local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'`
LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1)
fi
if [[ -z "$LATEST_VERSION" ]]; then
local ARCHIVE_NAME="wordpress-$WP_VERSION"
else
local ARCHIVE_NAME="wordpress-$LATEST_VERSION"
fi
else
local ARCHIVE_NAME="wordpress-$WP_VERSION"
fi
download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz
tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR
fi
download https://raw.githubusercontent.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
}
install_test_suite() {
# portable in-place argument for both GNU sed and Mac OSX sed
if [[ $(uname -s) == 'Darwin' ]]; then
local ioption='-i.bak'
else
local ioption='-i'
fi
# set up testing suite if it doesn't yet exist
if [ ! -d $WP_TESTS_DIR ]; then
# set up testing suite
mkdir -p $WP_TESTS_DIR
rm -rf $WP_TESTS_DIR/{includes,data}
check_svn_installed
svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data
fi
if [ ! -f wp-tests-config.php ]; then
download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
# remove all forward slashes in the end
WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::")
sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s:__DIR__ . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
fi
}
recreate_db() {
shopt -s nocasematch
if [[ $1 =~ ^(y|yes)$ ]]
then
mysqladmin drop $DB_NAME -f --user="$DB_USER" --password="$DB_PASS"$EXTRA
create_db
echo "Recreated the database ($DB_NAME)."
else
echo "Leaving the existing database ($DB_NAME) in place."
fi
shopt -u nocasematch
}
create_db() {
mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
}
install_db() {
if [ ${SKIP_DB_CREATE} = "true" ]; then
return 0
fi
# parse DB_HOST for port or socket references
local PARTS=(${DB_HOST//\:/ })
local DB_HOSTNAME=${PARTS[0]};
local DB_SOCK_OR_PORT=${PARTS[1]};
local EXTRA=""
if ! [ -z $DB_HOSTNAME ] ; then
if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then
EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
elif ! [ -z $DB_SOCK_OR_PORT ] ; then
EXTRA=" --socket=$DB_SOCK_OR_PORT"
elif ! [ -z $DB_HOSTNAME ] ; then
EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
fi
fi
# create database
if [ $(mysql --user="$DB_USER" --password="$DB_PASS"$EXTRA --execute='show databases;' | grep ^$DB_NAME$) ]
then
echo "Reinstalling will delete the existing test database ($DB_NAME)"
read -p 'Are you sure you want to proceed? [y/N]: ' DELETE_EXISTING_DB
recreate_db $DELETE_EXISTING_DB
else
create_db
fi
}
install_wp
install_test_suite
install_db

View File

@ -1,6 +1,6 @@
{ {
"name": "stklcode/stklcode-liveticker", "name": "stklcode/stklcode-liveticker",
"version": "1.3.0", "version": "1.1.1",
"description": "A simple Liveticker for Wordpress.", "description": "A simple Liveticker for Wordpress.",
"keywords": [ "keywords": [
"wordpress", "wordpress",
@ -17,17 +17,22 @@
], ],
"type": "wordpress-plugin", "type": "wordpress-plugin",
"require": { "require": {
"php": ">=7.2", "php": ">=5.6",
"composer/installers": "~v2.3.0" "composer/installers": "~1.7"
}, },
"require-dev": { "require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^v1.0", "php": ">=7",
"matthiasmullie/minify": "^1.3", "consolidation/robo": "^2",
"phpcompatibility/phpcompatibility-wp": "^2.1", "phpunit/phpunit": "^9",
"phpunit/php-code-coverage": "^9",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7",
"slowprog/composer-copy-file": "~0.3", "slowprog/composer-copy-file": "~0.3",
"squizlabs/php_codesniffer": "^3.11", "squizlabs/php_codesniffer": "^3.5",
"wp-coding-standards/wpcs": "^3.1", "phpcompatibility/php-compatibility": "^9.3",
"yoast/wp-test-utils": "^1.2" "wp-coding-standards/wpcs": "^2.3",
"patchwork/jsqueeze": "^2.0",
"natxet/cssmin": "^3.0",
"matthiasmullie/minify": "^1.3"
}, },
"scripts": { "scripts": {
"post-install-cmd": [ "post-install-cmd": [
@ -37,7 +42,16 @@
"@minify" "@minify"
], ],
"build": [ "build": [
"@minify" "@minify",
"robo build"
],
"package": [
"@minify",
"robo package"
],
"deploy": [
"@minify",
"robo deploy:all"
], ],
"test": [ "test": [
"phpunit" "phpunit"
@ -64,11 +78,5 @@
"minifyjs scripts/block.js > scripts/block.min.js", "minifyjs scripts/block.js > scripts/block.min.js",
"minifyjs scripts/liveticker.js > scripts/liveticker.min.js" "minifyjs scripts/liveticker.js > scripts/liveticker.min.js"
] ]
},
"config": {
"allow-plugins": {
"composer/installers": true,
"dealerdirect/phpcodesniffer-composer-installer": true
}
} }
} }

4418
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@ class Admin extends SCLiveticker {
* *
* @return void * @return void
*/ */
public static function dashboard_right_now(): void { public static function dashboard_right_now() {
$total_files = wp_count_posts( 'scliveticker_tick' ); $total_files = wp_count_posts( 'scliveticker_tick' );
echo '<tr>'; echo '<tr>';
@ -37,24 +37,181 @@ class Admin extends SCLiveticker {
* *
* @return void * @return void
*/ */
public static function register_settings_page(): void { public static function register_settings_page() {
add_submenu_page( add_submenu_page(
'edit.php?post_type=scliveticker_tick', 'edit.php?post_type=scliveticker_tick',
'Liveticker ' . __( 'Settings', 'stklcode-liveticker' ), 'Liveticker ' . __( 'Settings', 'stklcode-liveticker' ),
__( 'Settings', 'stklcode-liveticker' ), __( 'Settings', 'stklcode-liveticker' ),
'manage_options', 'manage_options',
'scliveticker_settings', 'scliveticker_settings',
array( Settings::class, 'render_settings_page' ) array(
__CLASS__,
'settings_page',
)
); );
} }
/**
* Register settings API
*
* @return void
*/
public static function register_settings() {
register_setting(
'scliveticker_settings',
self::OPTION,
array( __CLASS__, 'validate_settings' )
);
// Form sections.
add_settings_section(
'scliveticker_settings_general',
__( 'General', 'stklcode-liveticker' ),
array( __CLASS__, 'settings_general_section' ),
'scliveticker-settings-page'
);
// Form fields.
add_settings_field(
'enable_ajax',
__( 'Use AJAX', 'stklcode-liveticker' ),
array( __CLASS__, 'settings_enable_ajax_field' ),
'scliveticker-settings-page',
'scliveticker_settings_general',
array( 'label_for' => esc_attr( self::OPTION ) . '-enable-ajax' )
);
add_settings_field(
'poll_interval',
__( 'AJAX poll interval', 'stklcode-liveticker' ),
array( __CLASS__, 'settings_poll_interval_field' ),
'scliveticker-settings-page',
'scliveticker_settings_general',
array( 'label_for' => esc_attr( self::OPTION ) . '-poll-interval' )
);
add_settings_field(
'enable_css',
__( 'Default CSS Styles', 'stklcode-liveticker' ),
array( __CLASS__, 'settings_enable_css_field' ),
'scliveticker-settings-page',
'scliveticker_settings_general',
array( 'label_for' => esc_attr( self::OPTION ) . '-enable-css' )
);
add_settings_field(
'show_feed',
__( 'Show RSS feed', 'stklcode-liveticker' ),
array( __CLASS__, 'settings_show_feed_field' ),
'scliveticker-settings-page',
'scliveticker_settings_general',
array( 'label_for' => esc_attr( self::OPTION ) . '-show-feed' )
);
}
/**
* Render general section.
*
* @return void
*/
public static function settings_general_section() {
}
/**
* Render uninstall section.
*
* @return void
*/
public static function settings_uninstall_section() {
}
/**
* Render enable AJAX field.
*
* @return void
*/
public static function settings_enable_ajax_field() {
$checked = self::$options['enable_ajax'];
echo '<input id="' . esc_attr( self::OPTION ) . '-enable-ajax" type="checkbox" name="' . esc_attr( self::OPTION ) . '[enable_ajax]" value="1" ' . checked( $checked, 1, false ) . '> ';
esc_html_e( 'Enable', 'stklcode-liveticker' );
echo '<p class="description">' . esc_html__( 'Disable this option to not use AJAX update. This means all liveticker widgets and shortcodes are only updated once on site load.', 'stklcode-liveticker' ) . '</p>';
}
/**
* Render AJAX poll interval field.
*
* @return void
*/
public static function settings_poll_interval_field() {
$poll_interval = self::$options['poll_interval'];
echo '<input id="' . esc_attr( self::OPTION ) . '-poll-interval" type="number" name="' . esc_attr( self::OPTION ) . '[poll_interval]" value="' . esc_attr( $poll_interval ) . '"> ';
esc_html_e( 'seconds', 'stklcode-liveticker' );
echo '<p class="description">' . esc_html__( 'Interval (in seconds) to update ticker if AJAX is enabled.', 'stklcode-liveticker' ) . '</p>';
}
/**
* Render enable css field.
*
* @return void
*/
public static function settings_enable_css_field() {
$checked = self::$options['enable_css'];
echo '<input id="' . esc_attr( self::OPTION ) . '-enable-css" type="checkbox" name="' . esc_attr( self::OPTION ) . '[enable_css]" value="1" ' . checked( $checked, 1, false ) . ' /> ';
esc_html_e( 'Enable', 'stklcode-liveticker' );
echo '<p class="description">' . esc_html__( 'Disable this option to remove the default styling CSS file.', 'stklcode-liveticker' ) . '</p>';
}
/**
* Render enable css field.
*
* @return void
*/
public static function settings_show_feed_field() {
$checked = self::$options['show_feed'];
echo '<input id="' . esc_attr( self::OPTION ) . '-show-feed" type="checkbox" name="' . esc_attr( self::OPTION ) . '[show_feed]" value="1" ' . checked( $checked, 1, false ) . ' /> ';
esc_html_e( 'Enable', 'stklcode-liveticker' );
echo '<p class="description">' . esc_html__( 'Can be overwritten in shortcode.', 'stklcode-liveticker' ) . '</p>';
}
/**
* Render the settings page.
*
* @return void
*/
public static function settings_page() {
include SCLIVETICKER_DIR . 'views/settings-page.php';
}
/**
* Validate settings callback.
*
* @param array $input Input arguments.
*
* @return array Parsed arguments.
*/
public static function validate_settings( $input ) {
$defaults = self::default_options();
$result['enable_ajax'] = isset( $input['enable_ajax'] ) ? intval( $input['enable_ajax'] ) : 0;
$result['poll_interval'] = isset( $input['poll_interval'] ) ? intval( $input['poll_interval'] ) : $defaults['poll_interval'];
$result['enable_css'] = isset( $input['enable_css'] ) ? intval( $input['enable_css'] ) : 0;
$result['show_feed'] = isset( $input['show_feed'] ) ? intval( $input['show_feed'] ) : 0;
return $result;
}
/** /**
* Register custom Gutenberg block type. * Register custom Gutenberg block type.
* *
* @return void * @return void
* @since 1.1 * @since 1.1
*/ */
public static function register_block(): void { public static function register_block() {
wp_register_script( wp_register_script(
'scliveticker-editor', 'scliveticker-editor',
SCLIVETICKER_BASE . 'scripts/block.min.js', SCLIVETICKER_BASE . 'scripts/block.min.js',

View File

@ -1,102 +0,0 @@
<?php
/**
* Liveticker: Plugin API class.
*
* This file contains the plugin's REST API extensions.
*
* @package SCLiveticker
*/
namespace SCLiveticker;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
use DateTime;
use WP_REST_Request;
/**
* Liveticker.
*
* @since 1.2
*/
class Api {
/**
* Initialize custom fields for REST API responses.
*
* @return void
*/
public static function init(): void {
// Add rendered modification date to WP_Post object.
register_rest_field(
'scliveticker_tick',
'modified_rendered',
array(
'get_callback' => function ( $post ) {
return ( new DateTime( $post['modified'] ) )->format( 'd.m.Y H:i' );
},
'schema' => array(
'description' => __( 'Rendered modification date and time.', 'stklcode-liveticker' ),
'type' => 'string',
),
)
);
}
/**
* Filter tick queries by ticker slug and date.
*
* @param array $args Query vars.
* @param WP_REST_Request $request The REST request.
*
* @return array Filtered query values.
*/
public static function tick_query_filter( array $args, WP_REST_Request $request ): array {
// Extract arguments.
$ticker_slug = $request->get_param( 'ticker' );
$limit = intval( $request->get_param( 'limit' ) );
$last_poll = $request->get_param( 'last' );
if ( ! empty( $ticker_slug ) ) {
$args['tax_query'][] = array(
'taxonomy' => 'scliveticker_ticker',
'field' => 'slug',
'terms' => $ticker_slug,
);
}
if ( $limit > 0 ) {
$args['posts_per_page'] = $limit;
$args['paged'] = 1;
} elseif ( $limit < 0 ) {
$args['nopaging'] = true;
$args['paged'] = 0;
}
if ( ! empty( $last_poll ) ) {
$last_poll = explode(
',',
gmdate(
'Y,m,d,H,i,s',
rest_parse_date( $last_poll )
)
);
$args['date_query'] = array(
'column' => 'post_date_gmt',
'after' => array(
'year' => intval( $last_poll[0] ),
'month' => intval( $last_poll[1] ),
'day' => intval( $last_poll[2] ),
'hour' => intval( $last_poll[3] ),
'minute' => intval( $last_poll[4] ),
'second' => intval( $last_poll[5] ),
),
);
}
return $args;
}
}

View File

@ -26,7 +26,7 @@ class SCLiveticker {
* *
* @var string OPTIONS * @var string OPTIONS
*/ */
const VERSION = '1.3.0'; const VERSION = '1.1.1';
/** /**
* Options tag. * Options tag.
@ -62,7 +62,7 @@ class SCLiveticker {
* *
* @return void * @return void
*/ */
public static function init(): void { public static function init() {
// Skip on autosave. // Skip on autosave.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return; return;
@ -71,17 +71,13 @@ class SCLiveticker {
// Load plugin options. // Load plugin options.
self::update_options(); self::update_options();
// Add filter for REST API queries. // Skip on AJAX if not enabled disabled.
add_filter( 'rest_api_init', array( 'SCLiveticker\\Api', 'init' ) );
add_filter( 'rest_scliveticker_tick_query', array( 'SCLiveticker\\Api', 'tick_query_filter' ), 10, 2 );
// Skip on AJAX if not enabled.
if ( ( ! isset( self::$options['enable_ajax'] ) || 1 !== self::$options['enable_ajax'] ) && ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) { if ( ( ! isset( self::$options['enable_ajax'] ) || 1 !== self::$options['enable_ajax'] ) && ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
return; return;
} }
// Load Textdomain. // Load Textdomain.
load_plugin_textdomain( 'stklcode-liveticker' ); load_plugin_textdomain( 'stklcode-liveticker', false );
// Allow shortcodes in widgets. // Allow shortcodes in widgets.
add_filter( 'widget_text', 'do_shortcode' ); add_filter( 'widget_text', 'do_shortcode' );
@ -104,7 +100,7 @@ class SCLiveticker {
add_action( 'right_now_content_table_end', array( 'SCLiveticker\\Admin', 'dashboard_right_now' ) ); add_action( 'right_now_content_table_end', array( 'SCLiveticker\\Admin', 'dashboard_right_now' ) );
// Settings. // Settings.
add_action( 'admin_init', array( 'SCLiveticker\\Settings', 'register_settings' ) ); add_action( 'admin_init', array( 'SCLiveticker\\Admin', 'register_settings' ) );
add_action( 'admin_menu', array( 'SCLiveticker\\Admin', 'register_settings_page' ) ); add_action( 'admin_menu', array( 'SCLiveticker\\Admin', 'register_settings_page' ) );
} }
} }
@ -114,7 +110,7 @@ class SCLiveticker {
* *
* @return void * @return void
*/ */
public static function register_types(): void { public static function register_types() {
// Add new taxonomy, make it hierarchical (like categories). // Add new taxonomy, make it hierarchical (like categories).
$labels = array( $labels = array(
'name' => _x( 'Ticker', 'taxonomy general name', 'stklcode-liveticker' ), 'name' => _x( 'Ticker', 'taxonomy general name', 'stklcode-liveticker' ),
@ -182,7 +178,7 @@ class SCLiveticker {
* *
* @return string * @return string
*/ */
public static function shortcode_ticker_show( array $atts ): string { public static function shortcode_ticker_show( $atts ) {
// Indicate presence of shortcode (to enqueue styles/scripts later). // Indicate presence of shortcode (to enqueue styles/scripts later).
self::$shortcode_present = true; self::$shortcode_present = true;
@ -205,6 +201,16 @@ class SCLiveticker {
} else { } else {
$show_feed = 1 === self::$options['show_feed']; $show_feed = 1 === self::$options['show_feed'];
} }
$output = '<div class="wp-block-scliveticker-ticker';
if ( 1 === self::$options['enable_ajax'] ) {
$output .= ' sclt-ajax" '
. 'data-sclt-ticker="' . $ticker . '" '
. 'data-sclt-limit="' . $limit . '" '
. 'data-sclt-last="' . time();
}
$output .= '"><ul>';
$args = array( $args = array(
'post_type' => 'scliveticker_tick', 'post_type' => 'scliveticker_tick',
'posts_per_page' => $limit, 'posts_per_page' => $limit,
@ -219,32 +225,20 @@ class SCLiveticker {
$wp_query = new WP_Query( $args ); $wp_query = new WP_Query( $args );
$ticks = '';
$last = null;
while ( $wp_query->have_posts() ) { while ( $wp_query->have_posts() ) {
$wp_query->the_post(); $wp_query->the_post();
$ticks .= self::tick_html( get_the_time( 'd.m.Y H:i' ), get_the_title(), get_the_content(), get_the_ID() ); $output .= self::tick_html( get_the_time( 'd.m.Y H:i' ), get_the_title(), get_the_content() );
if ( is_null( $last ) ) {
$last = get_post_modified_time( 'Y-m-d\TH:i:s', true );
}
} }
$output = '<div class="wp-block-scliveticker-ticker'; $output .= '</ul></div>';
if ( 1 === self::$options['enable_ajax'] ) {
$output .= ' sclt-ajax" '
. 'data-sclt-ticker="' . esc_attr( $ticker ) . '" '
. 'data-sclt-limit="' . $limit . '" '
. 'data-sclt-last="' . $last;
}
$output .= '"><ul>' . $ticks . '</ul></div>';
// Show RSS feed link, if configured. // Show RSS feed link, if configured.
if ( $show_feed ) { if ( $show_feed ) {
$feed_link = get_post_type_archive_feed_link( 'scliveticker_tick' ) . ''; $feed_link = get_post_type_archive_feed_link( 'scliveticker_tick' ) . '';
if ( false === strpos( $feed_link, '&' ) ) { if ( false === strpos( $feed_link, '&' ) ) {
$feed_link .= '?scliveticker_ticker=' . rawurlencode( $ticker ); $feed_link .= '?scliveticker_ticker=' . $ticker;
} else { } else {
$feed_link .= '&scliveticker_ticker=' . rawurlencode( $ticker ); $feed_link .= '&scliveticker_ticker=' . $ticker;
} }
$output .= '<a href="' . esc_attr( $feed_link ) . '">Feed</a>'; $output .= '<a href="' . esc_attr( $feed_link ) . '">Feed</a>';
} }
@ -259,9 +253,9 @@ class SCLiveticker {
* @return void * @return void
* @since 1.1 Combined former methods "enqueue_styles" and "enqueue_scripts". * @since 1.1 Combined former methods "enqueue_styles" and "enqueue_scripts".
*/ */
public static function enqueue_resources(): void { public static function enqueue_resources() {
// Only add if shortcode is present. // Only add if shortcode is present.
if ( self::$shortcode_present || self::$widget_present || has_block( 'scliveticker/ticker' ) ) { if ( self::$shortcode_present || self::$widget_present || self::block_present() ) {
wp_enqueue_script( wp_enqueue_script(
'scliveticker-js', 'scliveticker-js',
SCLIVETICKER_BASE . 'scripts/liveticker.min.js', SCLIVETICKER_BASE . 'scripts/liveticker.min.js',
@ -273,13 +267,11 @@ class SCLiveticker {
// Add endpoint to script. // Add endpoint to script.
wp_localize_script( wp_localize_script(
'scliveticker-js', 'scliveticker-js',
'scliveticker', 'sclivetickerAjax',
array( array(
'ajax_url' => admin_url( 'admin-ajax.php' ), 'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'scliveticker_update-ticks' ), 'nonce' => wp_create_nonce( 'scliveticker_update-ticks' ),
'api' => rest_url(), 'poll_interval' => self::$options['poll_interval'] * 1000,
'embedded_script' => boolval( self::$options['embedded_script'] ),
'poll_interval' => self::$options['poll_interval'] * 1000,
) )
); );
@ -289,7 +281,8 @@ class SCLiveticker {
'sclt-css', 'sclt-css',
SCLIVETICKER_BASE . 'styles/liveticker.min.css', SCLIVETICKER_BASE . 'styles/liveticker.min.css',
'', '',
self::VERSION self::VERSION,
'all'
); );
} }
} }
@ -300,14 +293,14 @@ class SCLiveticker {
* *
* @return void * @return void
*/ */
public static function ajax_update(): void { public static function ajax_update() {
// Verify AJAX nonce. // Verify AJAX nonce.
check_ajax_referer( 'scliveticker_update-ticks' ); check_ajax_referer( 'scliveticker_update-ticks' );
// Extract update requests. // Extract update requests.
if ( isset( $_POST['update'] ) && is_array( $_POST['update'] ) ) { // Input var okay. if ( isset( $_POST['update'] ) && is_array( $_POST['update'] ) ) { // Input var okay.
$res = array(); $res = array();
// @codingStandardsIgnoreLine Sanitization of array handled on field level. // @codingStandardsIgnoreLine Sanitization of arrayhandled on field level.
foreach ( wp_unslash( $_POST['update'] ) as $update_req ) { foreach ( wp_unslash( $_POST['update'] ) as $update_req ) {
if ( is_array( $update_req ) && ( isset( $update_req['s'] ) || isset( $update_req['w'] ) ) ) { if ( is_array( $update_req ) && ( isset( $update_req['s'] ) || isset( $update_req['w'] ) ) ) {
if ( isset( $update_req['s'] ) ) { if ( isset( $update_req['s'] ) ) {
@ -317,7 +310,7 @@ class SCLiveticker {
$is_widget = true; $is_widget = true;
$slug = sanitize_text_field( $update_req['w'] ); $slug = sanitize_text_field( $update_req['w'] );
} else { } else {
// Should never occur, but for completeness' sake... // Should never occur, but for completenes' sake...
break; break;
} }
@ -360,9 +353,9 @@ class SCLiveticker {
while ( $query->have_posts() ) { while ( $query->have_posts() ) {
$query->the_post(); $query->the_post();
if ( $is_widget ) { if ( $is_widget ) {
$out .= self::tick_html_widget( get_the_time( 'd.m.Y H:i' ), get_the_title(), false, get_the_ID() ); $out .= self::tick_html_widget( get_the_time( 'd.m.Y H:i' ), get_the_title(), false );
} else { } else {
$out .= self::tick_html( get_the_time( 'd.m.Y H:i' ), get_the_title(), get_the_content(), get_the_ID() ); $out .= self::tick_html( get_the_time( 'd.m.Y H:i' ), get_the_title(), get_the_content(), $is_widget );
} }
} }
@ -393,19 +386,18 @@ class SCLiveticker {
* *
* @return void * @return void
*/ */
public static function mark_widget_present(): void { public static function mark_widget_present() {
self::$widget_present = true; self::$widget_present = true;
} }
/** /**
* Update options. * Update options.
* *
* @return void * @param array $options Optional. New options to save.
* *
* @since 1.0.0 * @return void
* @since 1.3.0 removed unused parameter
*/ */
protected static function update_options(): void { protected static function update_options( $options = null ) {
self::$options = wp_parse_args( self::$options = wp_parse_args(
get_option( self::OPTION ), get_option( self::OPTION ),
self::default_options() self::default_options()
@ -417,37 +409,31 @@ class SCLiveticker {
* *
* @return array The options array. * @return array The options array.
*/ */
protected static function default_options(): array { protected static function default_options() {
return array( return array(
'enable_ajax' => 1, 'enable_ajax' => 1,
'poll_interval' => 60, 'poll_interval' => 60,
'enable_css' => 1, 'enable_css' => 1,
'show_feed' => 0, 'show_feed' => 0,
'enable_shortcode' => 0, 'reset_settings' => 0,
'embedded_script' => 0,
'reset_settings' => 0,
); );
} }
/** /**
* Generate HTML code for a tick element. * Generate HTML code for a tick element.
* *
* @param string $time Tick time (readable). * @param string $time Tick time (readable).
* @param string $title Tick title. * @param string $title Tick title.
* @param string $content Tick content. * @param string $content Tick content.
* @param integer $id Tick ID. * @param boolean $is_widget Is the code for Widget.
* *
* @return string HTML code of tick. * @return string HTML code of tick.
*/ */
private static function tick_html( string $time, string $title, string $content, int $id ): string { private static function tick_html( $time, $title, $content, $is_widget = false ) {
if ( self::$options['enable_shortcode'] ) { return '<li class="sclt-tick">'
$content = do_shortcode( $content ); . '<p><span class="sclt-tick_time">' . esc_html( $time ) . '</span>'
} . '<span class="sclt-tick-title">' . esc_html( $title ) . '</span></p>'
. '<p class="sclt-tick-content">' . $content . '</p></li>';
return '<li class="sclt-tick" data-sclt-tick-id="' . esc_attr( $id ) . '">'
. '<span class="sclt-tick-time">' . esc_html( $time ) . '</span>'
. '<span class="sclt-tick-title">' . esc_html( $title ) . '</span>'
. '<div class="sclt-tick-content">' . $content . '</div></li>';
} }
/** /**
@ -456,21 +442,28 @@ class SCLiveticker {
* @param string $time Tick time (readable). * @param string $time Tick time (readable).
* @param string $title Tick title. * @param string $title Tick title.
* @param boolean $highlight Highlight element. * @param boolean $highlight Highlight element.
* @param integer $id Tick ID.
* *
* @return string HTML code of widget tick. * @return string HTML code of widget tick.
*/ */
public static function tick_html_widget( string $time, string $title, bool $highlight, int $id = 0 ): string { public static function tick_html_widget( $time, $title, $highlight ) {
$out = '<li'; $out = '<li';
if ( $highlight ) { if ( $highlight ) {
$out .= ' class="sclt-widget-new"'; $out .= ' class="sclt-widget-new"';
} }
if ( $id > 0 ) {
$out .= ' data-sclt-tick-id="' . esc_attr( $id ) . '"';
}
return $out . '>' return $out . '>'
. '<span class="sclt-widget-time">' . esc_html( $time ) . '</span>' . '<span class="sclt-widget-time">' . esc_html( $time ) . '</span>'
. '<span class="sclt-widget-title">' . $title . '</span>' . '<span class="sclt-widget-title">' . $title . '</span>'
. '</li>'; . '</li>';
} }
/**
* Check if the Gutenberg block is present in current post.
*
* @return boolean True, if Gutenberg block is present.
* @since 1.1
*/
private static function block_present() {
return function_exists( 'has_block' ) && // We are in WP 5.x environment.
has_block( 'scliveticker/ticker' ); // Specific block is present.
}
} }

View File

@ -1,305 +0,0 @@
<?php
/**
* Liveticker: Plugin settings class.
*
* This file contains the derived class for the plugin's settings.
*
* @package SCLiveticker
*/
namespace SCLiveticker;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Liveticker admin configuration.
*
* @since 1.3.0 extracted from {@link Admin} class
*/
class Settings extends SCLiveticker {
/**
* Register settings API
*
* @return void
*
* @since 1.0.0
* @since 1.3.0 moved from Admin to Settings class
*/
public static function register_settings(): void {
register_setting(
'scliveticker_settings',
self::OPTION,
array( __CLASS__, 'validate_settings' )
);
// Form sections.
add_settings_section(
'scliveticker_settings_general',
__( 'General', 'stklcode-liveticker' ),
array( __CLASS__, 'settings_general_section' ),
'scliveticker-settings-page'
);
// Form fields.
add_settings_field(
'enable_ajax',
__( 'Use AJAX', 'stklcode-liveticker' ),
array( __CLASS__, 'settings_enable_ajax_field' ),
'scliveticker-settings-page',
'scliveticker_settings_general',
array( 'label_for' => esc_attr( self::OPTION ) . '-enable-ajax' )
);
add_settings_field(
'poll_interval',
__( 'AJAX poll interval', 'stklcode-liveticker' ),
array( __CLASS__, 'settings_poll_interval_field' ),
'scliveticker-settings-page',
'scliveticker_settings_general',
array( 'label_for' => esc_attr( self::OPTION ) . '-poll-interval' )
);
add_settings_field(
'enable_css',
__( 'Default CSS Styles', 'stklcode-liveticker' ),
array( __CLASS__, 'settings_enable_css_field' ),
'scliveticker-settings-page',
'scliveticker_settings_general',
array( 'label_for' => esc_attr( self::OPTION ) . '-enable-css' )
);
add_settings_field(
'show_feed',
__( 'Show RSS feed', 'stklcode-liveticker' ),
array( __CLASS__, 'settings_show_feed_field' ),
'scliveticker-settings-page',
'scliveticker_settings_general',
array( 'label_for' => esc_attr( self::OPTION ) . '-show-feed' )
);
add_settings_field(
'enable_shortcode',
__( 'Shortcode support', 'stklcode-liveticker' ),
array( __CLASS__, 'settings_enable_shortcode_field' ),
'scliveticker-settings-page',
'scliveticker_settings_general',
array( 'label_for' => esc_attr( self::OPTION ) . '-enable-shortcode' )
);
add_settings_field(
'embedded_script',
__( 'Embedded JavaScript', 'stklcode-liveticker' ),
array( __CLASS__, 'settings_embedded_script_field' ),
'scliveticker-settings-page',
'scliveticker_settings_general',
array( 'label_for' => esc_attr( self::OPTION ) . '-embedded-script' )
);
}
/**
* Render the settings page.
*
* @return void
*
* @since 1.0.0
* @since 1.3.0 moved from Admin to Settings class
*/
public static function render_settings_page(): void {
?>
<div class="wrap">
<div id="icon-options-general" class="icon32"><br></div>
<h2>Liveticker <?php esc_html_e( 'Settings', 'stklcode-liveticker' ); ?></h2>
<?php
if ( isset( $_GET['settings-updated'] ) ) { // phpcs:ignore
echo '<div class="updated"><p>' . esc_html__( 'Settings updated successfully.', 'stklcode-liveticker' ) . '</p></div>';
}
?>
<form action="options.php" method="post">
<?php
settings_fields( 'scliveticker_settings' );
do_settings_sections( 'scliveticker-settings-page' );
submit_button();
?>
</form>
</div>
<?php
}
/**
* Render general section.
*
* @return void
*
* @since 1.0.0
* @since 1.3.0 moved from Admin to Settings class
*/
public static function settings_general_section(): void {
}
/**
* Render enable AJAX field.
*
* @return void
*
* @since 1.0.0
* @since 1.3.0 moved from Admin to Settings class
*/
public static function settings_enable_ajax_field(): void {
self::render_checkbox(
'enable-ajax',
'[enable_ajax]',
self::$options['enable_ajax'],
__( 'Disable this option to not use AJAX update. This means all liveticker widgets and shortcodes are only updated once on site load.', 'stklcode-liveticker' ),
__( 'Enable AJAX updates', 'stklcode-liveticker' )
);
}
/**
* Render AJAX poll interval field.
*
* @return void
*
* @since 1.0.0
* @since 1.3.0 moved from Admin to Settings class
*/
public static function settings_poll_interval_field(): void {
$poll_interval = self::$options['poll_interval'];
echo '<input id="' . esc_attr( self::OPTION ) . '-poll-interval" type="number" name="' . esc_attr( self::OPTION ) . '[poll_interval]" value="' . esc_attr( $poll_interval ) . '"> ';
esc_html_e( 'seconds', 'stklcode-liveticker' );
echo '<p class="description">' . esc_html__( 'Interval (in seconds) to update ticker if AJAX is enabled.', 'stklcode-liveticker' ) . '</p>';
}
/**
* Render enable css field.
*
* @return void
*
* @since 1.0.0
* @since 1.3.0 moved from Admin to Settings class
*/
public static function settings_enable_css_field(): void {
self::render_checkbox(
'enable-css',
'[enable_css]',
self::$options['enable_css'],
__( 'Disable this option to remove the default styling CSS file.', 'stklcode-liveticker' ),
__( 'Enable default stylesheet', 'stklcode-liveticker' )
);
}
/**
* Render enable css field.
*
* @return void
*
* @since 1.0.0
* @since 1.3.0 moved from Admin to Settings class
*/
public static function settings_show_feed_field(): void {
self::render_checkbox(
'show-feed',
'[show_feed]',
self::$options['show_feed'],
__( 'Can be overwritten in shortcode.', 'stklcode-liveticker' ),
__( 'Show RSS feed in shortcode', 'stklcode-liveticker' )
);
}
/**
* Render enable shortcode field.
*
* @return void
*
* @since 1.2.0
* @since 1.3.0 moved from Admin to Settings class
*/
public static function settings_enable_shortcode_field(): void {
self::render_checkbox(
'enable-shortcode',
'[enable_shortcode]',
self::$options['enable_shortcode'],
__( 'Enable shortcode processing in tick content.', 'stklcode-liveticker' ),
__( 'Allow shortcodes in tick content', 'stklcode-liveticker' )
);
}
/**
* Render embedded script field.
*
* @return void
*
* @since 1.2.0
* @since 1.3.0 moved from Admin to Settings class
*/
public static function settings_embedded_script_field(): void {
self::render_checkbox(
'embedded-script',
'[embedded_script]',
self::$options['embedded_script'],
__( 'Allow embedded script evaluation in tick contents. This might be useful for embedded content, e.g. social media integrations.', 'stklcode-liveticker' ) .
' ' .
__( 'Be aware that this feature potentially enables cross-site scripting, so make sure content is created by trusted people and only enable this if required.', 'stklcode-liveticker' ),
__( 'Allow JavaScript in tick content', 'stklcode-liveticker' )
);
}
/**
* Validate settings callback.
*
* @param array $input Input arguments.
*
* @return array Parsed arguments.
*
* @since 1.0.0
* @since 1.3.0 moved from Admin to Settings class
*/
public static function validate_settings( array $input ): array {
$defaults = self::default_options();
$result['enable_ajax'] = isset( $input['enable_ajax'] ) ? intval( $input['enable_ajax'] ) : 0;
$result['poll_interval'] = isset( $input['poll_interval'] ) ? intval( $input['poll_interval'] ) : $defaults['poll_interval'];
$result['enable_css'] = isset( $input['enable_css'] ) ? intval( $input['enable_css'] ) : 0;
$result['show_feed'] = isset( $input['show_feed'] ) ? intval( $input['show_feed'] ) : 0;
$result['enable_shortcode'] = isset( $input['enable_shortcode'] ) ? intval( $input['enable_shortcode'] ) : 0;
$result['embedded_script'] = isset( $input['embedded_script'] ) ? intval( $input['embedded_script'] ) : 0;
return $result;
}
/**
* Render a checkbox field.
*
* @param string $id Field ID.
* @param string $name Option name.
* @param mixed $value Current value.
* @param string $description Description text.
* @param string $screen_reader_text Screen reader text.
*
* @return void
*/
private static function render_checkbox(
string $id,
string $name,
$value,
string $description,
string $screen_reader_text
) {
?>
<fieldset>
<legend class="screen-reader-text"><?php echo esc_html( $screen_reader_text ); ?></legend>
<label for="<?php echo esc_attr( self::OPTION . '-' . $id ); ?>">
<input id="<?php echo esc_attr( self::OPTION . '-' . $id ); ?>" name="<?php echo esc_attr( self::OPTION . $name ); ?>" type="checkbox" value="1" <?php checked( $value, 1 ); ?>>
<?php esc_html_e( 'Enable', 'stklcode-liveticker' ); ?>
</label>
<p class="description"><?php echo esc_html( $description ); ?></p>
</fieldset>
<?php
}
}

View File

@ -28,7 +28,7 @@ class System extends SCLiveticker {
* *
* @return void * @return void
*/ */
public static function activate(): void { public static function activate() {
// Load current options. // Load current options.
self::update_options(); self::update_options();
@ -49,7 +49,7 @@ class System extends SCLiveticker {
* *
* @return void * @return void
*/ */
public static function uninstall(): void { public static function uninstall() {
// Delete all ticks. // Delete all ticks.
$ticks = new WP_Query( array( 'post_type' => 'scliveticker_tick' ) ); $ticks = new WP_Query( array( 'post_type' => 'scliveticker_tick' ) );
foreach ( $ticks->get_posts() as $tick ) { foreach ( $ticks->get_posts() as $tick ) {

View File

@ -31,7 +31,7 @@ class Widget extends WP_Widget {
/** /**
* Register the widget. * Register the widget.
*/ */
public static function register(): void { public static function register() {
register_widget( __CLASS__ ); register_widget( __CLASS__ );
} }
@ -49,10 +49,10 @@ class Widget extends WP_Widget {
SCLiveticker::mark_widget_present(); SCLiveticker::mark_widget_present();
$instance = self::fill_options_with_defaults( $instance ); $instance = self::fill_options_with_defaults( $instance );
$before_widget = $args['before_widget'] ?? ''; $before_widget = isset( $args['before_widget'] ) ? $args['before_widget'] : '';
$after_widget = $args['after_widget'] ?? ''; $after_widget = isset( $args['after_widget'] ) ? $args['after_widget'] : '';
$before_title = $args['before_title'] ?? ''; $before_title = isset( $args['before_title'] ) ? $args['before_title'] : '';
$after_title = $args['after_title'] ?? ''; $after_title = isset( $args['after_title'] ) ? $args['after_title'] : '';
$title = apply_filters( 'scliveticker_catlit', $instance['title'] ); $title = apply_filters( 'scliveticker_catlit', $instance['title'] );
$category = apply_filters( 'scliveticker_catlit', $instance['category'] ); $category = apply_filters( 'scliveticker_catlit', $instance['category'] );
$count = apply_filters( 'scliveticker_catlit', $instance['count'] ); $count = apply_filters( 'scliveticker_catlit', $instance['count'] );
@ -95,16 +95,14 @@ class Widget extends WP_Widget {
$wp_query = new WP_Query( $args ); $wp_query = new WP_Query( $args );
$cnt = 0; $cnt = 0;
while ( $wp_query->have_posts() && ( $count <= 0 || ++$cnt < $count ) ) { while ( $wp_query->have_posts() && ( $count <= 0 || ++ $cnt < $count ) ) {
$wp_query->the_post(); $wp_query->the_post();
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped // @codingStandardsIgnoreLine
echo SCLiveticker::tick_html_widget( echo SCLiveticker::tick_html_widget(
esc_html( get_the_time( 'd.m.Y - H:i' ) ), esc_html( get_the_time( 'd.m.Y - H:i' ) ),
get_the_title(), get_the_title(),
( '1' === $highlight && get_the_time( 'U' ) > ( time() - $highlight_time ) ), ( '1' === $highlight && get_the_time( 'U' ) > ( time() - $highlight_time ) )
get_the_ID()
); );
// phpcs:enable
} }
echo '</ul>'; echo '</ul>';
@ -146,13 +144,7 @@ class Widget extends WP_Widget {
$highlight = isset( $instance['highlight'] ) ? esc_attr( $instance['highlight'] ) : '0'; $highlight = isset( $instance['highlight'] ) ? esc_attr( $instance['highlight'] ) : '0';
$highlight_time = isset( $instance['highlight_time'] ) ? esc_attr( $instance['highlight_time'] ) : '0'; $highlight_time = isset( $instance['highlight_time'] ) ? esc_attr( $instance['highlight_time'] ) : '0';
$ajax = isset( $instance['ajax'] ) ? esc_attr( $instance['ajax'] ) : '0'; $ajax = isset( $instance['ajax'] ) ? esc_attr( $instance['ajax'] ) : '0';
$categories = get_terms( $categories = get_terms( 'scliveticker_ticker', 'orderby=name&order=ASC' );
array(
'taxonomy' => 'scliveticker_ticker',
'orderby' => 'name',
'order' => 'ASC',
)
);
include SCLIVETICKER_DIR . 'views/widget-form.php'; include SCLIVETICKER_DIR . 'views/widget-form.php';
} }
@ -164,7 +156,7 @@ class Widget extends WP_Widget {
* *
* @return array Complete instance configuration. * @return array Complete instance configuration.
*/ */
private static function fill_options_with_defaults( array $instance ): array { private static function fill_options_with_defaults( $instance ) {
$default = array( $default = array(
'title' => '', 'title' => '',
'category' => '', 'category' => '',

3521
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
{ {
"name": "stklcode-liveticker", "name": "stklcode-liveticker",
"version": "1.3.0", "version": "1.1.1",
"description": "A simple Liveticker for Wordpress.", "description": "A simple Liveticker for Wordpress.",
"author": "Stefan Kalscheuer", "author": "Stefan Kalscheuer",
"license": "GPL-2.0+", "license": "GPL-2.0+",
"devDependencies": { "devDependencies": {
"@wordpress/eslint-plugin": "^22", "@wordpress/eslint-plugin": "^7",
"@wordpress/stylelint-config": "^23", "@wordpress/stylelint-config": "^19",
"eslint": "^8", "eslint": "^7",
"stylelint": "^16" "stylelint": "^13"
} }
} }

View File

@ -12,7 +12,7 @@
<file>views</file> <file>views</file>
<!-- Compliance with WordPress Coding Standard --> <!-- Compliance with WordPress Coding Standard -->
<config name="minimum_supported_wp_version" value="5.0"/> <config name="minimum_supported_wp_version" value="4.0"/>
<rule ref="WordPress"> <rule ref="WordPress">
<exclude name="WordPress.DB.SlowDBQuery.slow_db_query_tax_query"/> <exclude name="WordPress.DB.SlowDBQuery.slow_db_query_tax_query"/>
</rule> </rule>
@ -25,6 +25,6 @@
</rule> </rule>
<!-- PHP compatibility level --> <!-- PHP compatibility level -->
<config name="testVersion" value="7.2-"/> <config name="testVersion" value="5.6-"/>
<rule ref="PHPCompatibilityWP"/> <rule ref="PHPCompatibility"/>
</ruleset> </ruleset>

View File

@ -1,19 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<phpunit bootstrap="tests/bootstrap.php" <phpunit bootstrap="./vendor/autoload.php">
backupGlobals="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true">
<testsuites> <testsuites>
<testsuite name="WP Liveticker 2 TestSuite"> <testsuite name="WP Liveticker 2 TestSuite">
<directory prefix="test-" suffix=".php">./tests/</directory> <directory suffix="-test.php">./test/</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">includes</directory>
</whitelist>
</filter>
</phpunit> </phpunit>

View File

@ -86,10 +86,6 @@
type: 'boolean', type: 'boolean',
default: false, default: false,
}, },
sort: {
type: 'string',
// implicit default: 'desc', left empty here for backwards compatibility of the block
},
}, },
edit: withSelect( function( select ) { edit: withSelect( function( select ) {
return { return {
@ -156,7 +152,7 @@
disabled: props.attributes.unlimited, disabled: props.attributes.unlimited,
value: props.attributes.limit, value: props.attributes.limit,
onChange: function( val ) { onChange: function( val ) {
props.setAttributes( { limit: Number( val ) } ); props.setAttributes( { limit: val } );
}, },
} }
), ),
@ -170,26 +166,6 @@
}, },
} }
), ),
el(
wp.components.SelectControl,
{
label: __( 'Output direction', 'stklcode-liveticker' ),
value: props.attributes.sort,
options: [
{
value: 'desc',
label: __( 'newest first', 'stklcode-liveticker' ),
},
{
value: 'asc',
label: __( 'oldest first', 'stklcode-liveticker' ),
},
],
onChange: function( val ) {
props.setAttributes( { sort: val } );
},
}
),
]; ];
} }
@ -207,7 +183,6 @@
'data-sclt-ticker': props.attributes.ticker, 'data-sclt-ticker': props.attributes.ticker,
'data-sclt-limit': props.attributes.unlimited ? 0 : props.attributes.limit, 'data-sclt-limit': props.attributes.unlimited ? 0 : props.attributes.limit,
'data-sclt-last': 0, 'data-sclt-last': 0,
'data-sclt-sort': props.attributes.sort,
} }
); );
}, },

View File

@ -1,12 +1,14 @@
/** /**
* Constructor of the scLiveticker object. * Contructor of the scLiveticker object.
* *
* @class * @class
*/ */
( function() { ( function() {
var apiURL; var ajaxURL = sclivetickerAjax.ajax_url;
var pollInterval; var nonce = sclivetickerAjax.nonce;
var pollInterval = sclivetickerAjax.poll_interval;
var ticker; var ticker;
var widgets;
/** /**
* Initialize iveticker JS component. * Initialize iveticker JS component.
@ -15,45 +17,69 @@
*/ */
var init = function() { var init = function() {
var updateNow = false; var updateNow = false;
var c = 0;
// Opt out if AJAX pobject not present. // Opt out if AJAX pobject not present.
if ( 'undefined' === typeof scliveticker ) { if ( 'undefined' === typeof sclivetickerAjax ) {
return; return;
} }
// Extract settings. // Extract AJAX settings.
apiURL = scliveticker.api + 'wp/v2/scliveticker_tick'; ajaxURL = sclivetickerAjax.ajax_url;
pollInterval = scliveticker.poll_interval; nonce = sclivetickerAjax.nonce;
pollInterval = sclivetickerAjax.poll_interval;
// Get ticker elements. // Get ticker elements.
ticker = [].map.call( ticker = [].map.call(
document.querySelectorAll( 'div.wp-block-scliveticker-ticker.sclt-ajax' ), document.querySelectorAll( 'div.wp-block-scliveticker-ticker.sclt-ajax' ),
function( elem ) { function( elem ) {
var o = parseElement( elem, false, ++c ); var list = elem.querySelector( 'ul' );
if ( '0' === o.lastPoll ) { var last = Number( elem.getAttribute( 'data-sclt-last' ) );
if ( ! list ) {
list = document.createElement( 'ul' );
elem.appendChild( list );
}
if ( 0 === last ) {
updateNow = true; updateNow = true;
} }
return o;
return {
s: elem.getAttribute( 'data-sclt-ticker' ),
l: elem.getAttribute( 'data-sclt-limit' ),
t: last,
e: list,
};
} }
); );
// Get widget elements. // Get widget elements.
ticker.concat( widgets = [].map.call(
[].map.call( document.querySelectorAll( 'div.wp-widget-scliveticker-ticker.sclt-ajax' ),
document.querySelectorAll( 'div.wp-widget-scliveticker-ticker.sclt-ajax' ), function( elem ) {
function( elem ) { var list = elem.querySelector( 'ul' );
var o = parseElement( elem, true, ++c ); var last = Number( elem.getAttribute( 'data-sclt-last' ) );
if ( '0' === o.lastPoll ) {
updateNow = true; if ( ! list ) {
} list = document.createElement( 'ul' );
return o; elem.appendChild( list );
} }
)
if ( 0 === last ) {
updateNow = true;
}
return {
w: elem.getAttribute( 'data-sclt-ticker' ),
l: elem.getAttribute( 'data-sclt-limit' ),
t: last,
e: list,
};
}
); );
// Trigger update, if necessary. // Trigger update, if necessary.
if ( ( 0 < ticker.length ) && 0 < pollInterval ) { if ( ( 0 < ticker.length || widgets.length ) && 0 < pollInterval ) {
if ( updateNow ) { if ( updateNow ) {
update(); update();
} else { } else {
@ -63,197 +89,100 @@
}; };
/** /**
* Parse an HTML element containing a liveticker. * Update liveticker on current page via AJAX call.
*
* @param {HTMLElement} elem The element.
* @param {boolean} widget Is the element a widget?
* @param {number} n Number of the container.
* @return {{ticker: string, lastPoll: string, ticks: any, limit: string, isWidget: *}} Ticker descriptor object.
*/
var parseElement = function( elem, widget, n ) {
var list = elem.querySelector( 'ul' );
var last = elem.getAttribute( 'data-sclt-last' );
var sort = elem.getAttribute( 'data-sclt-sort' );
elem.id = 'sclt-' + n;
if ( ! list ) {
list = document.createElement( 'ul' );
elem.appendChild( list );
} else {
[].forEach.call(
elem.querySelectorAll( 'li.sclt-tick' ),
function( li ) {
var id = li.getAttribute( 'data-sclt-tick-id' );
if ( id ) {
li.id = 'sclt-' + n + '-' + id;
li.removeAttribute( 'data-sclt-tick-id' );
}
}
);
}
if ( 'asc' !== sort && 'desc' !== 'sort' ) {
sort = 'desc';
}
return {
id: n,
ticker: elem.getAttribute( 'data-sclt-ticker' ),
limit: elem.getAttribute( 'data-sclt-limit' ),
lastPoll: last,
sort: sort,
ticks: list,
isWidget: widget,
updating: false,
};
};
/**
* Update liveticker on current page via REST API call.
* *
* @return {void} * @return {void}
*/ */
var update = function() { var update = function() {
// Iterate over available tickers. // Extract ticker-slug, limit and timestamp of last poll.
ticker.forEach( var updateReq = 'action=sclt_update-ticks&_ajax_nonce=' + nonce;
function( t ) { var i, j;
var xhr, query; var xhr = new XMLHttpRequest();
if ( t.updating ) { for ( i = 0; i < ticker.length; i++ ) {
// Do not update twice. updateReq = updateReq +
return; '&update[' + i + '][s]=' + ticker[ i ].s +
} '&update[' + i + '][l]=' + ticker[ i ].l +
'&update[' + i + '][t]=' + ticker[ i ].t;
}
for ( j = 0; j < widgets.length; j++ ) {
updateReq = updateReq +
'&update[' + ( i + j ) + '][w]=' + widgets[ j ].w +
'&update[' + ( i + j ) + '][l]=' + widgets[ j ].l +
'&update[' + ( i + j ) + '][t]=' + widgets[ j ].t;
}
t.updating = true; // Issue AJAX request.
xhr = new XMLHttpRequest(); xhr.open( 'POST', ajaxURL, true );
query = '?ticker=' + encodeURI( t.ticker ) + xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;' );
'&limit=' + encodeURI( t.limit ) + xhr.onreadystatechange = function() {
'&last=' + encodeURI( t.lastPoll ); var updateResp;
xhr.open( 'GET', apiURL + query, true ); if ( XMLHttpRequest.DONE === this.readyState && 200 === this.status ) {
xhr.addEventListener( try {
'load', updateResp = JSON.parse( this.responseText );
function() { if ( updateResp ) {
var updateResp; updateResp.forEach(
try { function( u ) {
updateResp = JSON.parse( this.responseText ); ticker.forEach(
if ( updateResp ) { function( t ) {
updateResp.reverse(); if ( t.s === u.s ) {
updateResp.forEach( t.t = u.t; // Update last poll timestamp.
function( u ) { updateHTML( t, u ); // Update HTML markup.
addTick( t, u ); }
}
);
widgets.forEach(
function( t ) {
if ( t.w === u.w ) {
t.t = u.t;
updateHTML( t, u );
}
} }
); );
} }
t.updating = false; );
} catch ( e ) {
// eslint-disable-next-line no-console
console.warn( 'Liveticker AJAX update failed, stopping automatic updates.' );
}
} }
); setTimeout( update, pollInterval ); // Re-trigger update.
xhr.send(); } catch ( e ) {
// eslint-disable-next-line no-console
console.warn( 'Liveticker AJAX update failed, stopping automatic updates.' );
}
} }
); };
xhr.send( updateReq );
// Re-trigger update.
setTimeout( update, pollInterval );
}; };
/** /**
* Do actual update of HTML code. * Do actual update of HTML code.
* *
* @param {Object} t Ticker or Widget reference. * @param {Object} t Ticker or Widget reference.
* @param {Object} u Update entity. * @param {number} t.l Limit of entries to display.
* @param {HTMLElement} t.e HTML element of the ticker/widget.
* @param {Object} u Update entity.
* @param {string} u.h HTML code to append.
* @param {number} u.t Timetsamp of last update.
* @return {void} * @return {void}
*/ */
var addTick = function( t, u ) { var updateHTML = function( t, u ) {
// Parse new DOM-part. // Parse new DOM-part.
var li = document.createElement( 'li' ); var n = document.createElement( 'ul' );
var time = document.createElement( 'span' ); n.innerHTML = u.h;
var title = document.createElement( 'span' );
var content = document.createElement( 'div' );
var cls = t.isWidget ? 'sclt-widget' : 'sclt-tick';
var old;
var scripts = [];
time.classList.add( cls + '-time' ); // Prepend new ticks to container.
time.innerText = u.modified_rendered; while ( n.hasChildNodes() ) {
title.classList.add( cls + '-title' ); t.e.prepend( n.lastChild );
title.innerText = u.title.rendered;
content.classList.add( cls + '-content' );
content.innerHTML = u.content.rendered;
// Process embedded scripts, if any.
if ( scliveticker.embedded_script ) {
Array.prototype.forEach.call(
content.getElementsByTagName( 'script' ),
function( script ) {
var script2;
if ( script.src ) {
// Move referenced scripts to page head.
script.parentNode.removeChild( script );
script2 = document.createElement( 'script' );
Array.prototype.forEach.call( script.attributes, function( a ) {
script2.setAttribute( a.nodeName, a.nodeValue );
} );
document.head.appendChild( script2 );
} else {
scripts.push( script );
}
}
);
} }
// Create the actual tick element. t.e.parentNode.setAttribute( 'data-sclt-last', u.t );
li.id = 'sclt-' + t.id + '-' + u.id;
li.classList.add( cls );
li.appendChild( time );
li.appendChild( title );
li.appendChild( content );
old = document.getElementById( 'sclt-' + t.id + '-' + u.id );
if ( old ) {
// Replace entry, if it already exists (i.e. has been updated).
t.ticks.replaceChild( li, old );
} else if ( 'asc' === t.sort ) {
// Append new tick as last element to container.
t.ticks.appendChild( li );
} else {
// Prepend new tick as fist element to container.
t.ticks.insertBefore( li, t.ticks.firstChild );
}
// Update last poll time.
t.lastPoll = u.date_gmt;
t.ticks.parentNode.setAttribute( 'data-sclt-last', u.date_gmt );
// Remove tail, if limit is set. // Remove tail, if limit is set.
if ( 0 < t.limit && t.limit < t.ticks.children.length ) { if ( 0 < t.l ) {
if ( 'asc' === t.sort ) { [].slice.call( t.e.getElementsByTagName( 'li' ), t.l ).forEach(
old = [].slice.call( t.ticks.children, 0, -t.limit ); function( li ) {
} else { li.remove();
old = [].slice.call( t.ticks.children, t.limit );
}
old.forEach(
function( l ) {
l.remove();
} }
); );
} }
// Evaluate embedded inline scripts.
// Directly evaluate script otherwise.
scripts.forEach( function( script ) {
try {
// eslint-disable-next-line no-eval
eval( script.innerHTML );
} catch ( e ) {
// eslint-disable-next-line no-console
console.warn( 'Failed to evaluate embedded script.' );
}
} );
}; };
document.addEventListener( document.addEventListener(

View File

@ -9,7 +9,7 @@
* @wordpress-plugin * @wordpress-plugin
* Plugin Name: Liveticker (by stklcode) * Plugin Name: Liveticker (by stklcode)
* Description: A simple Liveticker for WordPress. * Description: A simple Liveticker for WordPress.
* Version: 1.3.0 * Version: 1.1.1
* Author: Stefan Kalscheuer * Author: Stefan Kalscheuer
* Author URI: https://www.stklcode.de * Author URI: https://www.stklcode.de
* Text Domain: stklcode-liveticker * Text Domain: stklcode-liveticker
@ -26,7 +26,7 @@
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Liveticker (by stklcode). If not, see https://www.gnu.org/licenses/gpl-2.0.html. * along with Liveticker (by stklcode). If not, see http://www.gnu.org/licenses/gpl-2.0.html.
*/ */
// Exit if accessed directly. // Exit if accessed directly.
@ -64,24 +64,22 @@ spl_autoload_register( 'scliveticker_autoload' );
/** /**
* Autoloader for Liveticker classes. * Autoloader for Liveticker classes.
* *
* @param string $class_name Name of the class to load. * @param string $class Name of the class to load.
* *
* @return void * @return void
*/ */
function scliveticker_autoload( string $class_name ): void { function scliveticker_autoload( $class ) {
$plugin_classes = array( $plugin_classes = array(
'SCLiveticker\\SCLiveticker', 'SCLiveticker\\SCLiveticker',
'SCLiveticker\\Admin', 'SCLiveticker\\Admin',
'SCLiveticker\\Api',
'SCLiveticker\\Settings',
'SCLiveticker\\System', 'SCLiveticker\\System',
'SCLiveticker\\Widget', 'SCLiveticker\\Widget',
); );
if ( in_array( $class_name, $plugin_classes, true ) ) { if ( in_array( $class, $plugin_classes, true ) ) {
require_once sprintf( require_once sprintf(
'%s/includes/class-%s.php', '%s/includes/class-%s.php',
SCLIVETICKER_DIR, SCLIVETICKER_DIR,
strtolower( str_replace( '_', '-', substr( $class_name, 13 ) ) ) strtolower( str_replace( '_', '-', substr( $class, 13 ) ) )
); );
} }
} }

View File

@ -1,29 +0,0 @@
<?php
/**
* PHPUnit bootstrap file.
*
* @package SCLiveticker
*/
use Yoast\WPTestUtils\WPIntegration;
require_once dirname( __DIR__ ) . '/vendor/yoast/wp-test-utils/src/WPIntegration/bootstrap-functions.php';
$_tests_dir = WPIntegration\get_path_to_wp_test_dir();
// Get access to tests_add_filter() function.
require_once $_tests_dir . 'includes/functions.php';
// Add plugin to active mu-plugins to make sure it gets loaded.
tests_add_filter(
'muplugins_loaded',
function() {
require dirname( __DIR__ ) . '/stklcode-liveticker.php';
}
);
/*
* Bootstrap WordPress. This will also load the Composer autoload file, the PHPUnit Polyfills
* and the custom autoloader for the TestCase and the mock object classes.
*/
WPIntegration\bootstrap_it();

View File

@ -1,157 +0,0 @@
<?php
/**
* Liveticker: Plugin API tests.
*
* This file contains unit tests for the plugin's REST API extensions.
*
* @package SCLiveticker
*/
namespace SCLiveticker;
use DateInterval;
use DateTime;
use WP_REST_Request;
use WP_REST_Server;
use Yoast\WPTestUtils\WPIntegration\TestCase;
/**
* Class Test_API.
*/
class Test_API extends TestCase {
/**
* Initialize WP REST API for tests.
*
* @return void
*/
public function set_up(): void {
parent::set_up();
global $wp_rest_server;
$wp_rest_server = new WP_REST_Server();
do_action( 'rest_api_init' );
}
/**
* Test presence of registered routes for ticks and tickers.
*
* @return void
*/
public function test_register_route(): void {
global $wp_rest_server;
$routes = $wp_rest_server->get_routes();
self::assertArrayHasKey( '/wp/v2/scliveticker_tick', $routes, 'Ticks not exposed in API' );
self::assertArrayHasKey( '/wp/v2/scliveticker_tick/(?P<id>[\d]+)', $routes, 'Specific ticks not exposed in API' );
self::assertArrayHasKey( '/wp/v2/scliveticker_ticker', $routes, 'Tickers not exposed in API' );
self::assertArrayHasKey( '/wp/v2/scliveticker_ticker/(?P<id>[\d]+)', $routes, 'Specific tickers not exposed in API' );
}
/**
* Test fetching ticks and tickers via the REST API.
*
* @return void
*/
public function test_get_ticks(): void {
global $wp_rest_server;
$request = new WP_REST_Request( 'GET', '/wp/v2/scliveticker_tick' );
$response = $wp_rest_server->dispatch( $request );
self::assertEquals( 200, $response->get_status(), 'Unexpected status code' );
self::assertEmpty( $response->get_data(), 'No data expected on empty database' );
// Create two tickers with 10 ticks each.
wp_set_current_user( 1 );
$ticker_id = array(
1 => self::factory()->term->create(
array(
'name' => 'Ticker 1',
'description' => 'Test Liveticker 1',
'slug' => 'ticker1',
'taxonomy' => 'scliveticker_ticker',
)
),
2 >= self::factory()->term->create(
array(
'name' => 'Ticker 2',
'description' => 'Test Liveticker 2',
'slug' => 'ticker2',
'taxonomy' => 'scliveticker_ticker',
)
),
);
$dt = new DateTime( '2021-05-22 16:17:18' );
foreach ( range( 1, 20 ) as $n ) {
$t = 0 === $n % 2 ? '1' : '2';
$i = ceil( $n / 2 );
$p = self::factory()->post->create(
array(
'post_type' => 'scliveticker_tick',
'post_date_gmt' => $dt->format( 'Y-m-d H_i_s' ),
'post_title' => 'Tick ' . $t . '.' . $i,
'post_status' => 'publish',
'post_content' => 'Content of Tick ' . $t . '.' . $i,
)
);
wp_set_object_terms( $p, $ticker_id[ $t ], 'scliveticker_ticker' );
$dt->add( new DateInterval( 'PT1M' ) );
}
wp_set_current_user( 0 );
// Verify ticker presence via API.
$response = $wp_rest_server->dispatch( new WP_REST_Request( 'GET', '/wp/v2/scliveticker_ticker' ) );
self::assertEquals( 200, $response->get_status(), 'Unexpected status code' );
self::assertEquals( 2, count( $response->get_data() ), 'Unexpected number of tickers' );
// Query all entries. The "limit" parameter should overrule "per_page".
$request->set_param( 'limit', - 1 );
$response = $wp_rest_server->dispatch( $request );
self::assertEquals( 200, $response->get_status(), 'Unexpected status code' );
self::assertEquals( 20, count( $response->get_data() ), 'Unexpected number of ticks without filter' );
// Limit number of entries.
$request->set_param( 'limit', 12 );
$response = $wp_rest_server->dispatch( $request );
self::assertEquals( 200, $response->get_status(), 'Unexpected status code with limit' );
self::assertEquals( 12, count( $response->get_data() ), 'Unexpected number of ticks with limit' );
// Use built-in pagination.
$request->set_param( 'limit', null );
$request->set_param( 'per_page', 13 );
$response = $wp_rest_server->dispatch( $request );
self::assertEquals( 200, $response->get_status(), 'Unexpected status code for first page' );
self::assertEquals( 13, count( $response->get_data() ), 'Unexpected number of ticks for first page' );
self::assertEquals( 20, $response->get_headers()['X-WP-Total'], 'Unexpected total header' );
self::assertEquals( 2, $response->get_headers()['X-WP-TotalPages'], 'Unexpected pages header' );
$request->set_param( 'page', 2 );
$response = $wp_rest_server->dispatch( $request );
self::assertEquals( 200, $response->get_status(), 'Unexpected status code for second page' );
self::assertEquals( 7, count( $response->get_data() ), 'Unexpected number of ticks for second page' );
// Filter by time.
$request->set_param( 'limit', null );
$request->set_param( 'per_page', 10 );
$request->set_param( 'page', 1 );
$request->set_param( 'last', $dt->sub( new DateInterval( 'PT4M' ) )->format( 'Y-m-d H:i:s' ) );
$response = $wp_rest_server->dispatch( $request );
self::assertEquals( 200, $response->get_status(), 'Unexpected status code with time filter' );
self::assertEquals( 3, count( $response->get_data() ), 'Unexpected number of ticks with time filter' );
// Filter by ticker.
$request->set_param( 'last', null );
$request->set_param( 'ticker', 'ticker1' );
$response = $wp_rest_server->dispatch( $request );
self::assertEquals( 200, $response->get_status(), 'Unexpected status code with ticker filter' );
self::assertEquals( 10, count( $response->get_data() ), 'Unexpected number of ticks with ticker filter' );
self::assertEmpty(
array_filter(
$response->get_data(),
function ( $t ) use ( $ticker_id ) {
return 1 !== count( $t['scliveticker_ticker'] ) || ! in_array( $ticker_id[1], $t['scliveticker_ticker'], true );
}
),
'No tick from ticker 2 should be present filtering for ticker1'
);
}
}

29
views/settings-page.php Normal file
View File

@ -0,0 +1,29 @@
<?php
/**
* Liveticker: Settings page.
*
* This file contains the view model for the Plugin settings oage.
*
* @package Liveticker
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<div class="wrap">
<div id="icon-options-general" class="icon32"><br></div>
<h2>Liveticker <?php esc_html_e( 'Settings', 'stklcode-liveticker' ); ?></h2>
<?php
if ( isset( $_GET['settings-updated'] ) ) { // phpcs:ignore
echo '<div class="updated"><p>' . esc_html__( 'Settings updated successfully.', 'stklcode-liveticker' ) . '</p></div>';
}
?>
<form action="options.php" method="post">
<?php
settings_fields( 'scliveticker_settings' );
do_settings_sections( 'scliveticker-settings-page' );
submit_button();
?>
</form>
</div>

View File

@ -54,10 +54,10 @@ if ( ! defined( 'ABSPATH' ) ) {
<?php esc_html_e( 'all', 'stklcode-liveticker' ); ?> <?php esc_html_e( 'all', 'stklcode-liveticker' ); ?>
</option> </option>
<?php <?php
for ( $i = 1; $i <= 10; $i++ ) { for ( $i = 1; $i <= 10; $i ++ ) {
printf( printf(
'<option value="%d"%s>%d</option>', '<option value="%d"%s>%d</option>',
intval( $i ), $i,
( $i === $count ) ? ' selected' : '', ( $i === $count ) ? ' selected' : '',
intval( $i ) intval( $i )
); );