Compare commits

..

No commits in common. "develop" and "v1.0.0" have entirely different histories.

43 changed files with 1215 additions and 1945 deletions

View File

@ -1,23 +0,0 @@
/.git
/.github
/assets
/bin
/dist
/node_modules
/tests
/vendor
/.distignore
/.drone.yml
/.eslintrc.json
/.gitattributes
/.gitignore
/.phpunit.result.cache
/.stylelintrc.json
/.travis.yml
/composer.json
/composer.lock
/CONTRIBUTING.md
/package.json
/package-lock.json
/phpcs.xml
/phpunit.xml

View File

@ -1,28 +1,38 @@
kind: pipeline
name: default
type: docker
clone:
git:
image: plugins/git
depth: 1
skip_verify: true
steps:
- name: composer-install
image: composer:2
pipeline:
restore-cache:
image: drillster/drone-volume-cache
restore: true
mount:
- vendor
volumes:
- /var/lib/drone/cache:/cache
cache_key: [ DRONE_REPO_OWNER, DRONE_REPO_NAME, DRONE_BRANCH ]
pre-build:
image: composer
commands:
- composer install --ignore-platform-req=php
- name: lint-php
image: php:8.2
- composer install
test:
image: composer
commands:
- ./vendor/bin/phpcs
depends_on:
- composer-install
- name: node-install
image: node:22
- ./vendor/bin/robo test
test-style:
image: composer
commands:
- npm install
- name: lint-assets
image: node:22
commands:
- npx eslint scripts/block.js
- npx eslint scripts/liveticker.js
- npx stylelint styles/block.css
- npx stylelint styles/liveticker.css
depends_on:
- node-install
- ./vendor/bin/robo test:cs
rebuild-cache:
image: drillster/drone-volume-cache
rebuild: true
mount:
- vendor
volumes:
- /var/lib/drone/cache:/cache
cache_key: [ DRONE_REPO_OWNER, DRONE_REPO_NAME, DRONE_BRANCH ]

View File

@ -1,34 +1,3 @@
{
"env": {
"es6": false,
"browser": true
},
"globals": {
"scliveticker": "readonly",
"wp": "readonly"
},
"extends": [
"plugin:@wordpress/eslint-plugin/custom",
"plugin:@wordpress/eslint-plugin/es5",
"plugin:@wordpress/eslint-plugin/i18n"
],
"rules": {
"@wordpress/i18n-text-domain": [
"error",
{
"allowedTextDomain": [ "stklcode-liveticker" ]
}
]
},
"overrides": [
{
"files": [
"*"
],
"rules": {
"no-var": "off",
"object-shorthand": "off"
}
}
]
"extends": "./vendor/npm-asset/eslint-config-wordpress/index.js"
}

20
.gitattributes vendored
View File

@ -1,20 +0,0 @@
/assets export-ignore
/.github export-ignore
/.distignore export-ignore
/.drone.yml export-ignore
/.eslintrc.json export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.stylelintrc.json export-ignore
/.travis.yml export-ignore
/bin export-ignore
/composer.json export-ignore
/composer.lock export-ignore
/CONTRIBUTING.md export-ignore
/dist export-ignore
/package.json export-ignore
/package-lock.json 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

@ -1,29 +0,0 @@
name: Plugin asset/readme update
on:
push:
branches:
- stable
jobs:
master:
name: Push to stable
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: Install
run: composer install --no-interaction
- name: Clean README.md
run: tail -n +7 README.md > README.md.tmp && mv README.md.tmp README.md
- name: WordPress.org plugin asset/readme update
uses: 10up/action-wordpress-plugin-asset-update@stable
env:
SLUG: stklcode-liveticker
ASSETS_DIR: assets
README_NAME: README.md
SVN_PASSWORD: ${{ secrets.WP_SVN_PASSWORD }}
SVN_USERNAME: ${{ secrets.WP_SVN_USERNAME }}

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

@ -1,29 +0,0 @@
name: Deploy to WordPress.org
on:
push:
tags:
- "v*"
- "!v*-*"
jobs:
tag:
name: New tag
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: Install
run: composer install --no-interaction
- name: Clean README.md
run: tail -n +7 README.md > README.md.tmp && mv README.md.tmp README.md
- name: WordPress Plugin Deploy
uses: 10up/action-wordpress-plugin-deploy@stable
env:
SLUG: stklcode-liveticker
ASSETS_DIR: assets
SVN_PASSWORD: ${{ secrets.WP_SVN_PASSWORD }}
SVN_USERNAME: ${{ secrets.WP_SVN_USERNAME }}

5
.gitignore vendored
View File

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

View File

@ -1,3 +1,3 @@
{
"extends": "@wordpress/stylelint-config"
"extends": "stylelint-config-wordpress"
}

13
.travis.yml Normal file
View File

@ -0,0 +1,13 @@
language: php
dist: trusty
php:
- '5.5'
- '5.6'
- '7.0'
- '7.1'
- '7.2'
- '7.3'
before_script:
- composer install
script:
- composer test-all

View File

@ -111,10 +111,10 @@ We probably find a solution for that.
## Continuous Integration
Automated tests are run using [Travis CI](https://travis-ci.org/stklcode/wp-liveticker) for every commit including pull requests.
Automated tests are run using [Travis CI](https://travis-ci.org/stklcode/wp-liveticker2) for every commit including pull requests.
They ensure compatibility with the supported PHP versions and the WP Coding Standards.
There is also a semi-automated code quality analysis pushing results to [SonarCloud](https://sonarcloud.io/dashboard?id=de.stklcode.web.wordpress.plugins%3Awp-liveticker).
There is also a semi-automated code quality analysis pushing results to [SonarCloud](https://sonarcloud.io/dashboard?id=de.stklcode.web.wordpress.plugins%3Awp-liveticker2).
Keep in mind that the ruleset is not yet perfect, so not every minor issue has to be fixed immediately.
## Still Open Questions?

View File

@ -1,19 +1,13 @@
[![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)
[![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)
[![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)
[![License](https://img.shields.io/badge/license-GPL%20v2-blue.svg)](https://github.com/stklcode/wp-liveticker/blob/stable/LICENSE.md)
# Liveticker (by stklcode)
* Contributors: stklcode
* Contributors: Stefan Kalscheuer
* Tags: liveticker, feed, rss
* Requires at least: 5.0
* Tested up to: 6.7
* Requires PHP: 7.2
* Stable tag: 1.3.0
* Requires at least: 4.0
* Tested up to: 5.0
* Requires PHP: 5.2
* Stable tag: 1.0.0
* 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.
@ -27,31 +21,28 @@ Easily add multiple livetickers, add them to posts with shortcode or use them as
* Handle multiple Tickers
* Automatic update via AJAX
* RSS feed capability
* Gutenberg block and shortcode to display liveticker
* Shortcode to display liveticker
* Add ticker to sidebar widgets
* Ability to customize through CSS
* Ability to customise through CSS
* Localization support
## Installation
If you dont know how to install a plugin for WordPress, [heres how](https://wordpress.org/support/article/managing-plugins/).
You can obtain the plugin through fhe official WordPress plugin repository.
Alternatively you can also use _Copmposer_.
1. Upload `stklcode-liveticker` to the `/wp-content/plugins/` directory.
2. Activate the plugin through the 'Plugins' menu in WordPress.
3. Go to Liveticker menu to start.
### Requirements ###
* PHP 7.2 or above
* WordPress 5.0 or above
* PHP 5.2 or above
* WordPress 4.0 or above
## Frequently asked questions
### How do I display a liveticker on my post/page?
On WordPress 5 sites there is a Gutenberg Block available to embed a liveticker in your post.
You can also use the shortcode `[liveticker ticker="my-ticker"]` on WordPress 4 or classic-mode sites.
Use the shortcode `[liveticker ticker="my-ticker"]`.
If you want to define a custom tick limit, you might also add a limit with `[liveticker ticker="my-ticker" limit="10"]`.
### Can I use my own styles?
@ -61,11 +52,9 @@ You can deactivate the default stylesheet on the settings page and include your
### Does the liveticker work with caching?
If you activate AJAX updates (enabled by default), the JavaScript will automatically update the content, even when the
page is loaded from cached.
If AJAX is disabled, it depends on your update and caching intervals. If you update your ticker every 5 minutes, a
caching time of 12 hours obviously makes no sense.
It strongly depends on the use case.
If you update your ticker every 5 minutes, a caching time of 12 hours obviously makes no sense.
However the AJAX update will fetch the latest ticks and update cached tickers depending on the configured interval.
## Screenshots
@ -74,52 +63,11 @@ caching time of 12 hours obviously makes no sense.
2. Tick management
3. Ticker configuration.
4. Settings page
5. Gutenberg block
6. Example shortcode
7. Example widget
5. Example shortcode
6. Example widget
## 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
* "Ticker" taxonomy name is now translatable
### 1.1.0 - 2020-05-02
* Requires PHP 5.6 or above
* Use GMT for automatic updates
* Gutenberg Block available
* Ticks exposed through REST API
* Changed AJAX update logic for embedded media compatibility
### 1.0.0 - 2018-11-02
* Initial release

490
RoboFile.php Normal file
View File

@ -0,0 +1,490 @@
<?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';
/**
* 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
*
* @return void
*/
public function testCS() {
$this->say( 'Executing PHPCS tests...' );
$this->_exec( __DIR__ . '/vendor/bin/phpcs --standard=phpcs.xml -s' );
}
/**
* 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,
)
) {
$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();
}
$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' );
}
/**
* 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,
)
) {
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,
)
) {
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,
)
) {
$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,
)
) {
// 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,
)
) {
// 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,
)
) {
// 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,
)
) {
// 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: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 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",
"version": "1.3.0",
"name": "stklcode/wp-liveticker",
"version": "1.0.0",
"description": "A simple Liveticker for Wordpress.",
"keywords": [
"wordpress",
@ -17,17 +17,23 @@
],
"type": "wordpress-plugin",
"require": {
"php": ">=7.2",
"composer/installers": "~v2.3.0"
"php": ">=5.2",
"composer/installers": "~1.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^v1.0",
"php": ">=5.2",
"consolidation/robo": "^1.0.0",
"phpunit/phpunit": "*",
"phpunit/php-code-coverage": "*",
"dealerdirect/phpcodesniffer-composer-installer": "^0.4",
"slowprog/composer-copy-file": "~0.2",
"squizlabs/php_codesniffer": "^3.1",
"wimg/php-compatibility": "^8.0",
"wp-coding-standards/wpcs": "~0.14",
"patchwork/jsqueeze": "^2.0.5",
"natxet/CssMin": "^3.0.5",
"matthiasmullie/minify": "^1.3",
"phpcompatibility/phpcompatibility-wp": "^2.1",
"slowprog/composer-copy-file": "~0.3",
"squizlabs/php_codesniffer": "^3.11",
"wp-coding-standards/wpcs": "^3.1",
"yoast/wp-test-utils": "^1.2"
"npm-asset/eslint-config-wordpress": "^2.0"
},
"scripts": {
"post-install-cmd": [
@ -37,38 +43,39 @@
"@minify"
],
"build": [
"@minify"
"@minify",
"robo build"
],
"package": [
"@minify",
"robo package"
],
"deploy": [
"@minify",
"robo deploy:all"
],
"test-all": [
"@test",
"@test-cs"
],
"test": [
"phpunit"
],
"lint-all": [
"@lint-php",
"@lint-css",
"@lint-js"
],
"lint-php": [
"test-cs": [
"phpcs --standard=phpcs.xml -s"
],
"lint-css": [
"npx stylelint styles/block.css",
"npx stylelint styles/liveticker.css"
],
"lint-js": [
"npx eslint scripts/block.js",
"npx eslint scripts/liveticker.js"
"fix-cs": [
"phpcbf --standard=phpcs.xml"
],
"minify": [
"minifycss styles/block.css > styles/block.min.css",
"minifycss styles/liveticker.css > styles/liveticker.min.css",
"minifyjs scripts/block.js > scripts/block.min.js",
"minifyjs scripts/liveticker.js > scripts/liveticker.min.js"
]
},
"config": {
"allow-plugins": {
"composer/installers": true,
"dealerdirect/phpcodesniffer-composer-installer": true
}
"repositories": [
{
"type": "composer",
"url": "https://asset-packagist.org"
}
]
}

View File

@ -1,81 +0,0 @@
<?php
/**
* Liveticker: Plugin admin class.
*
* This file contains the derived class for the plugin's administration features.
*
* @package SCLiveticker
*/
namespace SCLiveticker;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Liveticker admin configuration.
*/
class Admin extends SCLiveticker {
/**
* Add to Right Now Widget
*
* @return void
*/
public static function dashboard_right_now(): void {
$total_files = wp_count_posts( 'scliveticker_tick' );
echo '<tr>';
echo '<td class="first b b-tags"><a href="edit.php?post_type=scliveticker_tick">' . esc_html( $total_files->publish ) . '</a></td>';
echo '<td class="t tags"><a href="edit.php?post_type=scliveticker_tick">' . esc_html__( 'Ticks', 'stklcode-liveticker' ) . '</a></td>';
echo '</tr>';
}
/**
* Register settings page.
*
* @return void
*/
public static function register_settings_page(): void {
add_submenu_page(
'edit.php?post_type=scliveticker_tick',
'Liveticker ' . __( 'Settings', 'stklcode-liveticker' ),
__( 'Settings', 'stklcode-liveticker' ),
'manage_options',
'scliveticker_settings',
array( Settings::class, 'render_settings_page' )
);
}
/**
* Register custom Gutenberg block type.
*
* @return void
* @since 1.1
*/
public static function register_block(): void {
wp_register_script(
'scliveticker-editor',
SCLIVETICKER_BASE . 'scripts/block.min.js',
array( 'wp-blocks', 'wp-element' ),
self::VERSION,
true
);
wp_register_style(
'scliveticker-editor',
SCLIVETICKER_BASE . 'styles/block.min.css',
array(),
self::VERSION
);
register_block_type(
'scliveticker-block/liveticker',
array(
'editor_script' => 'scliveticker-editor',
'editor_style' => 'scliveticker-editor',
)
);
}
}

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

@ -0,0 +1,205 @@
<?php
/**
* Liveticker: Plugin admin class.
*
* This file contains the derived class for the plugin's administration features.
*
* @package Liveticker
*/
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Liveticker admin configuration.
*/
class SCLiveticker_Admin extends SCLiveticker {
/**
* Add to Right Now Widget
*
* @return void
*/
public static function dashboard_right_now() {
$total_files = wp_count_posts( 'scliveticker_tick' );
echo '<tr>';
echo '<td class="first b b-tags"><a href="edit.php?post_type=scliveticker_tick">' . esc_html( $total_files->publish ) . '</a></td>';
echo '<td class="t tags"><a href="edit.php?post_type=scliveticker_tick">' . esc_html__( 'Ticks', 'stklcode-liveticker' ) . '</a></td>';
echo '</tr>';
}
/**
* Register settings page.
*
* @return void
*/
public static function register_settings_page() {
add_submenu_page(
'edit.php?post_type=scliveticker_tick',
'Liveticker ' . __( 'Settings', 'stklcode-liveticker' ),
__( 'Settings', 'stklcode-liveticker' ),
'manage_options',
'scliveticker_settings',
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;
}
}

View File

@ -4,13 +4,9 @@
*
* This file contains the derived class for the plugin's system operations.
*
* @package SCLiveticker
* @package Liveticker
*/
namespace SCLiveticker;
use WP_Query;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
@ -19,7 +15,7 @@ if ( ! defined( 'ABSPATH' ) ) {
/**
* Liveticker system configuration.
*/
class System extends SCLiveticker {
class SCLiveticker_System extends SCLiveticker {
/**
* Activation hook.
@ -28,14 +24,14 @@ class System extends SCLiveticker {
*
* @return void
*/
public static function activate(): void {
public static function activate() {
// Load current options.
self::update_options();
// Add default settings to database.
$defaults = self::default_options();
if ( self::$options['reset_settings'] ) {
if ( self::$_options['reset_settings'] ) {
// Reset requested, overwrite existing options with default.
update_option( self::OPTION, $defaults );
} else {
@ -49,7 +45,7 @@ class System extends SCLiveticker {
*
* @return void
*/
public static function uninstall(): void {
public static function uninstall() {
// Delete all ticks.
$ticks = new WP_Query( array( 'post_type' => 'scliveticker_tick' ) );
foreach ( $ticks->get_posts() as $tick ) {

View File

@ -4,22 +4,17 @@
*
* This file contains the liveticker widget.
*
* @package SCLiveticker
* @package Liveticker
*/
namespace SCLiveticker;
use WP_Query;
use WP_Widget;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Class Widget.
* Class SCLiveticker_Widget.
*/
class Widget extends WP_Widget {
class SCLiveticker_Widget extends WP_Widget {
/**
* SCLiveticker_Widget constructor.
@ -31,7 +26,7 @@ class Widget extends WP_Widget {
/**
* Register the widget.
*/
public static function register(): void {
public static function register() {
register_widget( __CLASS__ );
}
@ -49,10 +44,10 @@ class Widget extends WP_Widget {
SCLiveticker::mark_widget_present();
$instance = self::fill_options_with_defaults( $instance );
$before_widget = $args['before_widget'] ?? '';
$after_widget = $args['after_widget'] ?? '';
$before_title = $args['before_title'] ?? '';
$after_title = $args['after_title'] ?? '';
$before_widget = isset( $args['before_widget'] ) ? $args['before_widget'] : '';
$after_widget = isset( $args['after_widget'] ) ? $args['after_widget'] : '';
$before_title = isset( $args['before_title'] ) ? $args['before_title'] : '';
$after_title = isset( $args['after_title'] ) ? $args['after_title'] : '';
$title = apply_filters( 'scliveticker_catlit', $instance['title'] );
$category = apply_filters( 'scliveticker_catlit', $instance['category'] );
$count = apply_filters( 'scliveticker_catlit', $instance['count'] );
@ -73,14 +68,14 @@ class Widget extends WP_Widget {
echo $before_title . esc_html( $title ) . $after_title;
}
echo '<div class="wp-widget-scliveticker-ticker';
echo '<ul class="sclt-widget';
if ( '1' === $ajax ) {
echo ' sclt-ajax" '
echo ' sclt-widget-ajax" '
. 'data-sclt-ticker="' . esc_attr( $category ) . '" '
. 'data-sclt-limit="' . esc_attr( $count ) . '" '
. 'data-sclt-last="' . esc_attr( current_datetime()->getTimestamp() );
. 'data-sclt-last="' . esc_attr( current_time( 'timestamp' ) );
}
echo '"><ul class="sclt-widget">';
echo '">';
$args = array(
'post_type' => 'scliveticker_tick',
@ -95,16 +90,14 @@ class Widget extends WP_Widget {
$wp_query = new WP_Query( $args );
$cnt = 0;
while ( $wp_query->have_posts() && ( $count <= 0 || ++$cnt < $count ) ) {
while ( $wp_query->have_posts() && ( $count <= 0 || ++ $cnt < $count ) ) {
$wp_query->the_post();
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
// @codingStandardsIgnoreLine
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(),
( '1' === $highlight && get_the_time( 'U' ) > ( time() - $highlight_time ) ),
get_the_ID()
( '1' === $highlight && get_the_time( 'U' ) > ( time() - $highlight_time ) )
);
// phpcs:enable
}
echo '</ul>';
@ -146,13 +139,7 @@ class Widget extends WP_Widget {
$highlight = isset( $instance['highlight'] ) ? esc_attr( $instance['highlight'] ) : '0';
$highlight_time = isset( $instance['highlight_time'] ) ? esc_attr( $instance['highlight_time'] ) : '0';
$ajax = isset( $instance['ajax'] ) ? esc_attr( $instance['ajax'] ) : '0';
$categories = get_terms(
array(
'taxonomy' => 'scliveticker_ticker',
'orderby' => 'name',
'order' => 'ASC',
)
);
$categories = get_terms( 'scliveticker_ticker', 'orderby=name&order=ASC' );
include SCLIVETICKER_DIR . 'views/widget-form.php';
}
@ -164,7 +151,7 @@ class Widget extends WP_Widget {
*
* @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(
'title' => '',
'category' => '',

View File

@ -4,19 +4,14 @@
*
* This file contains the plugin's base class.
*
* @package SCLiveticker
* @package Liveticker
*/
namespace SCLiveticker;
use WP_Query;
// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Liveticker.
*/
@ -26,7 +21,7 @@ class SCLiveticker {
*
* @var string OPTIONS
*/
const VERSION = '1.3.0';
const VERSION = '1.0.0';
/**
* Options tag.
@ -38,9 +33,9 @@ class SCLiveticker {
/**
* Plugin options.
*
* @var array $options
* @var array $_options
*/
protected static $options;
protected static $_options;
/**
* Marker if shortcode is present.
@ -62,7 +57,7 @@ class SCLiveticker {
*
* @return void
*/
public static function init(): void {
public static function init() {
// Skip on autosave.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
@ -71,17 +66,13 @@ class SCLiveticker {
// Load plugin options.
self::update_options();
// Add filter for REST API queries.
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 ) ) {
// Skip on AJAX if not enabled disabled.
if ( ( ! isset( self::$_options['enable_ajax'] ) || 1 !== self::$_options['enable_ajax'] ) && ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
return;
}
// Load Textdomain.
load_plugin_textdomain( 'stklcode-liveticker' );
load_plugin_textdomain( 'stklcode-liveticker', false );
// Allow shortcodes in widgets.
add_filter( 'widget_text', 'do_shortcode' );
@ -89,11 +80,14 @@ class SCLiveticker {
// Add shortcode.
add_shortcode( 'liveticker', array( __CLASS__, 'shortcode_ticker_show' ) );
// Enqueue styles and JavaScript.
add_action( 'wp_footer', array( __CLASS__, 'enqueue_resources' ) );
// Enqueue styles.
add_action( 'wp_footer', array( __CLASS__, 'enqueue_styles' ) );
// Enqueue JavaScript.
add_action( 'wp_footer', array( __CLASS__, 'enqueue_scripts' ) );
// Add AJAX hook if configured.
if ( 1 === self::$options['enable_ajax'] ) {
if ( 1 === self::$_options['enable_ajax'] ) {
add_action( 'wp_ajax_sclt_update-ticks', array( __CLASS__, 'ajax_update' ) );
add_action( 'wp_ajax_nopriv_sclt_update-ticks', array( __CLASS__, 'ajax_update' ) );
}
@ -101,11 +95,11 @@ class SCLiveticker {
// Admin only actions.
if ( is_admin() ) {
// Add dashboard "right now" functionality.
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.
add_action( 'admin_init', array( 'SCLiveticker\\Settings', 'register_settings' ) );
add_action( 'admin_menu', array( 'SCLiveticker\\Admin', 'register_settings_page' ) );
add_action( 'admin_init', array( 'SCLiveticker_Admin', 'register_settings' ) );
add_action( 'admin_menu', array( 'SCLiveticker_Admin', 'register_settings_page' ) );
}
}
@ -114,11 +108,11 @@ class SCLiveticker {
*
* @return void
*/
public static function register_types(): void {
public static function register_types() {
// Add new taxonomy, make it hierarchical (like categories).
$labels = array(
'name' => _x( 'Ticker', 'taxonomy general name', 'stklcode-liveticker' ),
'singular_name' => _x( 'Ticker', 'taxonomy singular name', 'stklcode-liveticker' ),
'name' => _x( 'Ticker', 'taxonomy general name' ),
'singular_name' => _x( 'Ticker', 'taxonomy singular name' ),
'search_items' => __( 'Search Tickers', 'stklcode-liveticker' ),
'all_items' => __( 'All Tickers', 'stklcode-liveticker' ),
'parent_item' => __( 'Parent Ticker', 'stklcode-liveticker' ),
@ -139,7 +133,6 @@ class SCLiveticker {
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'show_in_rest' => true,
)
);
@ -169,7 +162,6 @@ class SCLiveticker {
'supports' => array( 'title', 'editor', 'author' ),
'taxonomies' => array( 'scliveticker_ticker' ),
'has_archive' => true,
'show_in_rest' => true,
);
register_post_type( 'scliveticker_tick', $args );
@ -182,7 +174,7 @@ class SCLiveticker {
*
* @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).
self::$shortcode_present = true;
@ -203,8 +195,18 @@ class SCLiveticker {
if ( isset( $atts['feed'] ) ) {
$show_feed = 'true' === strtolower( $atts['feed'] ) || '1' === $atts['feed'];
} else {
$show_feed = 1 === self::$options['show_feed'];
$show_feed = 1 === self::$_options['show_feed'];
}
$output = '<ul class="sclt-ticker';
if ( 1 === self::$_options['enable_ajax'] ) {
$output .= ' sclt-ticker-ajax" '
. 'data-sclt-ticker="' . $ticker . '" '
. 'data-sclt-limit="' . $limit . '" '
. 'data-sclt-last="' . current_time( 'timestamp' );
}
$output .= '">';
$args = array(
'post_type' => 'scliveticker_tick',
'posts_per_page' => $limit,
@ -219,32 +221,20 @@ class SCLiveticker {
$wp_query = new WP_Query( $args );
$ticks = '';
$last = null;
while ( $wp_query->have_posts() ) {
$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() );
if ( is_null( $last ) ) {
$last = get_post_modified_time( 'Y-m-d\TH:i:s', true );
}
$output .= self::tick_html( get_the_time( 'd.m.Y H.i' ), get_the_title(), get_the_content() );
}
$output = '<div class="wp-block-scliveticker-ticker';
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>';
$output .= '</ul>';
// Show RSS feed link, if configured.
if ( $show_feed ) {
$feed_link = get_post_type_archive_feed_link( 'scliveticker_tick' ) . '';
if ( false === strpos( $feed_link, '&' ) ) {
$feed_link .= '?scliveticker_ticker=' . rawurlencode( $ticker );
$feed_link .= '?scliveticker_ticker=' . $ticker;
} else {
$feed_link .= '&scliveticker_ticker=' . rawurlencode( $ticker );
$feed_link .= '&scliveticker_ticker=' . $ticker;
}
$output .= '<a href="' . esc_attr( $feed_link ) . '">Feed</a>';
}
@ -253,15 +243,31 @@ class SCLiveticker {
return $output;
}
/**
* Register frontend CSS.
*
* @return void
*/
public static function enqueue_styles() {
// Only add if shortcode is present.
if ( self::$shortcode_present || self::$widget_present ) {
wp_enqueue_style(
'wplt-css',
SCLIVETICKER_BASE . 'styles/liveticker.min.css',
'',
self::VERSION, 'all'
);
}
}
/**
* Register frontend JS.
*
* @return void
* @since 1.1 Combined former methods "enqueue_styles" and "enqueue_scripts".
*/
public static function enqueue_resources(): void {
public static function enqueue_scripts() {
// Only add if shortcode is present.
if ( self::$shortcode_present || self::$widget_present || has_block( 'scliveticker/ticker' ) ) {
if ( self::$shortcode_present || self::$widget_present ) {
wp_enqueue_script(
'scliveticker-js',
SCLIVETICKER_BASE . 'scripts/liveticker.min.js',
@ -273,25 +279,13 @@ class SCLiveticker {
// Add endpoint to script.
wp_localize_script(
'scliveticker-js',
'scliveticker',
'sclivetickerAjax',
array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'scliveticker_update-ticks' ),
'api' => rest_url(),
'embedded_script' => boolval( self::$options['embedded_script'] ),
'poll_interval' => self::$options['poll_interval'] * 1000,
'poll_interval' => self::$_options['poll_interval'] * 1000,
)
);
// Enqueue CSS if enabled.
if ( 1 === self::$options['enable_css'] ) {
wp_enqueue_style(
'sclt-css',
SCLIVETICKER_BASE . 'styles/liveticker.min.css',
'',
self::VERSION
);
}
}
}
@ -300,14 +294,14 @@ class SCLiveticker {
*
* @return void
*/
public static function ajax_update(): void {
public static function ajax_update() {
// Verify AJAX nonce.
check_ajax_referer( 'scliveticker_update-ticks' );
// Extract update requests.
if ( isset( $_POST['update'] ) && is_array( $_POST['update'] ) ) { // Input var okay.
$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 ) {
if ( is_array( $update_req ) && ( isset( $update_req['s'] ) || isset( $update_req['w'] ) ) ) {
if ( isset( $update_req['s'] ) ) {
@ -317,18 +311,12 @@ class SCLiveticker {
$is_widget = true;
$slug = sanitize_text_field( $update_req['w'] );
} else {
// Should never occur, but for completeness' sake...
// Should never occur, but for completenes' sake...
break;
}
$limit = ( isset( $update_req['l'] ) ) ? intval( $update_req['l'] ) : - 1;
$last_poll = explode(
',',
gmdate(
'Y,m,d,H,i,s',
( isset( $update_req['t'] ) ) ? intval( $update_req['t'] ) : 0
)
);
$last_poll = ( isset( $update_req['t'] ) ) ? intval( $update_req['t'] ) : 0;
// Query new ticks from DB.
$query_args = array(
@ -342,15 +330,7 @@ class SCLiveticker {
),
),
'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] ),
),
'after' => date( 'c', $last_poll ),
),
);
@ -360,9 +340,9 @@ class SCLiveticker {
while ( $query->have_posts() ) {
$query->the_post();
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 {
$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 );
}
}
@ -370,13 +350,13 @@ class SCLiveticker {
$res[] = array(
'w' => $slug,
'h' => $out,
't' => time(),
't' => current_time( 'timestamp' ),
);
} else {
$res[] = array(
's' => $slug,
'h' => $out,
't' => time(),
't' => current_time( 'timestamp' ),
);
}
}
@ -393,20 +373,19 @@ class SCLiveticker {
*
* @return void
*/
public static function mark_widget_present(): void {
public static function mark_widget_present() {
self::$widget_present = true;
}
/**
* Update options.
*
* @return void
* @param array $options Optional. New options to save.
*
* @since 1.0.0
* @since 1.3.0 removed unused parameter
* @return void
*/
protected static function update_options(): void {
self::$options = wp_parse_args(
protected static function update_options( $options = null ) {
self::$_options = wp_parse_args(
get_option( self::OPTION ),
self::default_options()
);
@ -417,14 +396,12 @@ class SCLiveticker {
*
* @return array The options array.
*/
protected static function default_options(): array {
protected static function default_options() {
return array(
'enable_ajax' => 1,
'poll_interval' => 60,
'enable_css' => 1,
'show_feed' => 0,
'enable_shortcode' => 0,
'embedded_script' => 0,
'reset_settings' => 0,
);
}
@ -435,19 +412,15 @@ class SCLiveticker {
* @param string $time Tick time (readable).
* @param string $title Tick title.
* @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.
*/
private static function tick_html( string $time, string $title, string $content, int $id ): string {
if ( self::$options['enable_shortcode'] ) {
$content = do_shortcode( $content );
}
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>';
private static function tick_html( $time, $title, $content, $is_widget = false ) {
return '<li class="sclt-tick">'
. '<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>';
}
/**
@ -456,18 +429,14 @@ class SCLiveticker {
* @param string $time Tick time (readable).
* @param string $title Tick title.
* @param boolean $highlight Highlight element.
* @param integer $id Tick ID.
*
* @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';
if ( $highlight ) {
$out .= ' class="sclt-widget-new"';
}
if ( $id > 0 ) {
$out .= ' data-sclt-tick-id="' . esc_attr( $id ) . '"';
}
return $out . '>'
. '<span class="sclt-widget-time">' . esc_html( $time ) . '</span>'
. '<span class="sclt-widget-title">' . $title . '</span>'

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
}
}

88
package-lock.json generated Normal file
View File

@ -0,0 +1,88 @@
{
"name": "wp-liveticker2",
"version": "1.0.0-beta",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"cssesc": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-1.0.1.tgz",
"integrity": "sha512-S2hzrpWvE6G/rW7i7IxJfWBYn27QWfOIncUW++8Rbo1VB5zsJDSVPcnI+Q8z7rhxT6/yZeLOCja4cZnghJrNGA=="
},
"indexes-of": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
"integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc="
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
"postcss-media-query-parser": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz",
"integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ="
},
"postcss-resolve-nested-selector": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz",
"integrity": "sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4="
},
"postcss-selector-parser": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-4.0.0.tgz",
"integrity": "sha512-5h+MvEjnzu1qy6MabjuoPatsGAjjDV9B24e7Cktjl+ClNtjVjmvAXjOFQr1u7RlWULKNGYaYVE4s+DIIQ4bOGA==",
"requires": {
"cssesc": "^1.0.1",
"indexes-of": "^1.0.1",
"uniq": "^1.0.1"
}
},
"postcss-value-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
},
"stylelint-config-recommended": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-2.1.0.tgz",
"integrity": "sha512-ajMbivOD7JxdsnlS5945KYhvt7L/HwN6YeYF2BH6kE4UCLJR0YvXMf+2j7nQpJyYLZx9uZzU5G1ZOSBiWAc6yA=="
},
"stylelint-config-recommended-scss": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-3.2.0.tgz",
"integrity": "sha512-M8BFHMRf8KNz5EQPKJd8nMCGmBd2o5coDEObfHVbEkyLDgjIf1V+U5dHjaGgvhm0zToUxshxN+Gc5wpbOOew4g==",
"requires": {
"stylelint-config-recommended": "^2.0.0"
}
},
"stylelint-config-wordpress": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/stylelint-config-wordpress/-/stylelint-config-wordpress-13.1.0.tgz",
"integrity": "sha512-dpKj2/d3/XjDVoOvQzd54GoM8Rj5zldluOZKkVhBCc4JYMc6r1VYL5hpcgIjqy/i2Hyqg4Rh7zTafE/2AWq//w==",
"requires": {
"stylelint-config-recommended": "^2.1.0",
"stylelint-config-recommended-scss": "^3.2.0",
"stylelint-scss": "^3.3.0"
}
},
"stylelint-scss": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.3.2.tgz",
"integrity": "sha512-0x+nD1heoMJYOfi3FfGcz3Hrwhcm+Qyq+BuvoBv5v9xrZZ1aziRXQauuhjwb87gWAa9MBzxhfUqBnvTUrHlLjA==",
"requires": {
"lodash": "^4.17.10",
"postcss-media-query-parser": "^0.2.3",
"postcss-resolve-nested-selector": "^0.1.1",
"postcss-selector-parser": "^4.0.0",
"postcss-value-parser": "^3.3.0"
}
},
"uniq": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
"integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8="
}
}
}

View File

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

View File

@ -12,19 +12,12 @@
<file>views</file>
<!-- 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">
<exclude name="WordPress.DB.SlowDBQuery.slow_db_query_tax_query"/>
</rule>
<!-- Verify usage of the correct textdomain for WP translation -->
<rule ref="WordPress.WP.I18n">
<properties>
<property name="text_domain" type="array" value="stklcode-liveticker"/>
</properties>
<exclude name="WordPress.VIP.SlowDBQuery.slow_db_query_tax_query"/>
</rule>
<!-- PHP compatibility level -->
<config name="testVersion" value="7.2-"/>
<rule ref="PHPCompatibilityWP"/>
<config name="testVersion" value="5.2-"/>
<rule ref="PHPCompatibility"/>
</ruleset>

View File

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

View File

@ -1,215 +0,0 @@
/**
* stklcode-liveticker Gutenberg Block
*
* Gutenberg Block to integrate the liveticker widget without shortcode.
*/
( function() {
var __ = wp.i18n.__;
var registerBlockType = wp.blocks.registerBlockType;
var registerStore = wp.data.registerStore;
var withSelect = wp.data.withSelect;
var el = wp.element.createElement;
/**
* Datastore actions.
*/
var actions = {
setTickers: function( tickers ) {
return {
type: 'SET_TICKERS',
tickers: tickers,
};
},
getTickers: function( path ) {
return {
type: 'RECEIVE_TICKERS',
path: path,
};
},
};
registerStore( 'scliveticker/ticker', {
reducer: function( state, action ) {
if ( undefined === state ) {
state = { tickers: null };
}
switch ( action.type ) {
case 'SET_TICKERS':
state.tickers = action.tickers;
return state;
case 'RECEIVE_TICKERS':
return action.tickers;
}
return state;
},
actions: actions,
selectors: {
receiveTickers: function( state ) {
return state.tickers;
},
},
resolvers: {
receiveTickers: function() {
return wp.apiFetch( { path: '/wp/v2/scliveticker_ticker' } ).then( function( tickers ) {
return actions.setTickers( tickers.map( function( t ) {
return {
name: t.name,
slug: t.slug,
};
} ) );
} );
},
},
} );
registerBlockType( 'scliveticker/ticker', {
title: __( 'Liveticker', 'stklcode-liveticker' ),
icon: 'rss',
category: 'widgets',
keywords: [
__( 'Liveticker', 'stklcode-liveticker' ),
],
attributes: {
ticker: {
type: 'string',
default: '',
},
limit: {
type: 'number',
default: 5,
},
unlimited: {
type: 'boolean',
default: false,
},
sort: {
type: 'string',
// implicit default: 'desc', left empty here for backwards compatibility of the block
},
},
edit: withSelect( function( select ) {
return {
tickers: select( 'scliveticker/ticker' ).receiveTickers(),
};
} )( function( props ) {
var label = [
el(
wp.components.Dashicon,
{ icon: 'rss' }
),
__( 'Liveticker', 'stklcode-liveticker' ),
];
var content;
if ( null === props.tickers ) {
// Tickers not yet loaded.
content = [
el(
'span',
{ className: 'components-base-control label' },
label
),
el( wp.components.Spinner ),
];
} else if ( 0 === props.tickers.length ) {
// No tickers available.
content = [
el(
'span',
{ className: 'components-base-control label' },
label
),
el( 'span', null, __( 'No tickers available', 'stklcode-liveticker' ) ),
];
} else {
// Tickers loaded and available.
if ( 0 === props.attributes.ticker.length && props.tickers.length > 0 ) {
props.attributes.ticker = props.tickers[ 0 ].slug;
}
content = [
el(
wp.components.SelectControl,
{
label: label,
value: props.attributes.ticker,
options: props.tickers.map( function( t ) {
return {
value: t.slug,
label: t.name,
};
} ),
onChange: function( val ) {
props.setAttributes( { ticker: val } );
},
}
),
el(
wp.components.TextControl,
{
label: __( 'Number of Ticks', 'stklcode-liveticker' ),
type: 'number',
min: 1,
step: 1,
disabled: props.attributes.unlimited,
value: props.attributes.limit,
onChange: function( val ) {
props.setAttributes( { limit: Number( val ) } );
},
}
),
el(
wp.components.CheckboxControl,
{
label: __( 'unlimited', 'stklcode-liveticker' ),
checked: props.attributes.unlimited,
onChange: function( val ) {
props.setAttributes( { unlimited: val } );
},
}
),
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 } );
},
}
),
];
}
return el(
'div',
{ className: props.className + ' components-placeholder' },
content
);
} ),
save: function( props ) {
return el(
'div',
{
className: 'sclt-ajax',
'data-sclt-ticker': props.attributes.ticker,
'data-sclt-limit': props.attributes.unlimited ? 0 : props.attributes.limit,
'data-sclt-last': 0,
'data-sclt-sort': props.attributes.sort,
}
);
},
} );
}() );

View File

@ -1,265 +1,154 @@
/**
* Constructor of the scLiveticker object.
* Contructor of the scLiveticker object.
*
* @class
* @constructor
*/
( function() {
var apiURL;
var pollInterval;
var ticker;
function scLiveticker() {
}
/**
/**
* Initialize iveticker JS component.
*
* @return {void}
*/
var init = function() {
var updateNow = false;
var c = 0;
scLiveticker.init = function() {
// Opt out if AJAX pobject not present.
if ( 'undefined' === typeof scliveticker ) {
if ( 'undefined' === typeof sclivetickerAjax ) {
return;
}
// Extract settings.
apiURL = scliveticker.api + 'wp/v2/scliveticker_tick';
pollInterval = scliveticker.poll_interval;
// Extract AJAX settings.
scLiveticker.ajaxURL = sclivetickerAjax.ajax_url;
scLiveticker.nonce = sclivetickerAjax.nonce;
scLiveticker.pollInterval = sclivetickerAjax.poll_interval;
// Get ticker elements.
ticker = [].map.call(
document.querySelectorAll( 'div.wp-block-scliveticker-ticker.sclt-ajax' ),
scLiveticker.ticker = [].map.call(
document.querySelectorAll( 'ul.sclt-ticker-ajax' ),
function( elem ) {
var o = parseElement( elem, false, ++c );
if ( '0' === o.lastPoll ) {
updateNow = true;
}
return o;
return {
s: elem.getAttribute( 'data-sclt-ticker' ),
l: elem.getAttribute( 'data-sclt-limit' ),
t: elem.getAttribute( 'data-sclt-last' ),
e: elem
};
}
);
// Get widget elements.
ticker.concat(
[].map.call(
document.querySelectorAll( 'div.wp-widget-scliveticker-ticker.sclt-ajax' ),
scLiveticker.widgets = [].map.call(
document.querySelectorAll( 'ul.sclt-widget-ajax' ),
function( elem ) {
var o = parseElement( elem, true, ++c );
if ( '0' === o.lastPoll ) {
updateNow = true;
return {
w: elem.getAttribute( 'data-sclt-ticker' ),
l: elem.getAttribute( 'data-sclt-limit' ),
t: elem.getAttribute( 'data-sclt-last' ),
e: elem
};
}
return o;
}
)
);
// Trigger update, if necessary.
if ( ( 0 < ticker.length ) && 0 < pollInterval ) {
if ( updateNow ) {
update();
} else {
setTimeout( update, pollInterval );
if ( ( 0 < scLiveticker.ticker.length || scLiveticker.widgets.length ) && 0 < scLiveticker.pollInterval ) {
setTimeout( scLiveticker.update, scLiveticker.pollInterval );
}
}
};
};
/**
* Parse an HTML element containing a liveticker.
*
* @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.
/**
* Update liveticker on current page via AJAX call.
*
* @return {void}
*/
var update = function() {
// Iterate over available tickers.
ticker.forEach(
function( t ) {
var xhr, query;
scLiveticker.update = function() {
if ( t.updating ) {
// Do not update twice.
return;
// Extract ticker-slug, limit and timestamp of last poll.
var updateReq = 'action=sclt_update-ticks&_ajax_nonce=' + scLiveticker.nonce;
var i, j;
var xhr = new XMLHttpRequest();
for ( i = 0; i < scLiveticker.ticker.length; i++ ) {
updateReq = updateReq +
'&update[' + i + '][s]=' + scLiveticker.ticker[ i ].s +
'&update[' + i + '][l]=' + scLiveticker.ticker[ i ].l +
'&update[' + i + '][t]=' + scLiveticker.ticker[ i ].t;
}
for ( j = 0; j < scLiveticker.widgets.length; j++ ) {
updateReq = updateReq +
'&update[' + ( i + j ) + '][w]=' + scLiveticker.widgets[ j ].w +
'&update[' + ( i + j ) + '][l]=' + scLiveticker.widgets[ j ].l +
'&update[' + ( i + j ) + '][t]=' + scLiveticker.widgets[ j ].t;
}
t.updating = true;
xhr = new XMLHttpRequest();
query = '?ticker=' + encodeURI( t.ticker ) +
'&limit=' + encodeURI( t.limit ) +
'&last=' + encodeURI( t.lastPoll );
xhr.open( 'GET', apiURL + query, true );
xhr.addEventListener(
'load',
function() {
var updateResp;
// Issue AJAX request.
xhr.open( 'POST', scLiveticker.ajaxURL, true );
xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded;' );
xhr.onreadystatechange = function() {
var update;
if ( XMLHttpRequest.DONE === this.readyState && 200 === this.status ) {
try {
updateResp = JSON.parse( this.responseText );
if ( updateResp ) {
updateResp.reverse();
updateResp.forEach(
update = JSON.parse( this.responseText );
if ( update ) {
update.forEach(
function( u ) {
addTick( t, u );
scLiveticker.ticker.forEach(
function( t ) {
if ( t.s === u.s ) {
t.t = u.t; // Update last poll timestamp.
scLiveticker.updateHTML( t, u ); // Update HTML markup.
}
}
);
scLiveticker.widgets.forEach(
function( t ) {
if ( t.w === u.w ) {
t.t = u.t;
scLiveticker.updateHTML( t, u );
}
}
);
}
t.updating = false;
);
}
setTimeout( scLiveticker.update, scLiveticker.pollInterval ); // Re-trigger update.
} catch ( e ) {
// eslint-disable-next-line no-console
console.warn( 'Liveticker AJAX update failed, stopping automatic updates.' );
}
}
);
xhr.send();
}
);
// Re-trigger update.
setTimeout( update, pollInterval );
};
xhr.send( updateReq );
};
/**
/**
* Do actual update of HTML code.
*
* @param {Object} t Ticker or Widget reference.
* @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}
*/
var addTick = function( t, u ) {
// Parse new DOM-part.
var li = document.createElement( 'li' );
var time = document.createElement( 'span' );
var title = document.createElement( 'span' );
var content = document.createElement( 'div' );
var cls = t.isWidget ? 'sclt-widget' : 'sclt-tick';
var old;
var scripts = [];
scLiveticker.updateHTML = function( t, u ) {
time.classList.add( cls + '-time' );
time.innerText = u.modified_rendered;
title.classList.add( cls + '-title' );
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.
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 );
// Prepend HTML of new ticks.
t.e.innerHTML = u.h + t.e.innerHTML;
t.e.setAttribute( 'data-sclt-last', u.t );
// Remove tail, if limit is set.
if ( 0 < t.limit && t.limit < t.ticks.children.length ) {
if ( 'asc' === t.sort ) {
old = [].slice.call( t.ticks.children, 0, -t.limit );
} else {
old = [].slice.call( t.ticks.children, t.limit );
}
old.forEach(
function( l ) {
l.remove();
if ( 0 < t.l ) {
[].slice.call( t.e.getElementsByTagName( 'li' ), t.l ).forEach(
function( li ) {
li.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(
'DOMContentLoaded',
function() {
init(); // Trigger periodic update of livetickers.
scLiveticker.init(); // Trigger periodic update of livetickers.
}
);
}() );
);

View File

@ -9,7 +9,7 @@
* @wordpress-plugin
* Plugin Name: Liveticker (by stklcode)
* Description: A simple Liveticker for WordPress.
* Version: 1.3.0
* Version: 1.0.0
* Author: Stefan Kalscheuer
* Author URI: https://www.stklcode.de
* Text Domain: stklcode-liveticker
@ -26,7 +26,7 @@
* GNU General Public License for more details.
*
* 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.
@ -41,22 +41,19 @@ define( 'SCLIVETICKER_BASE', plugin_dir_url( __FILE__ ) );
define( 'SCLIVETICKER_BASENAME', plugin_basename( __FILE__ ) );
// System Hooks.
add_action( 'init', array( 'SCLiveticker\\SCLiveticker', 'register_types' ) );
add_action( 'plugins_loaded', array( 'SCLiveticker\\SCLiveticker', 'init' ) );
register_activation_hook( SCLIVETICKER_FILE, array( 'SCLiveticker\\System', 'activate' ) );
register_uninstall_hook( SCLIVETICKER_FILE, array( 'SCLiveticker\\System', 'uninstall' ) );
add_action( 'init', array( 'SCLiveticker', 'register_types' ) );
add_action( 'plugins_loaded', array( 'SCLiveticker', 'init' ) );
register_activation_hook( SCLIVETICKER_FILE, array( 'SCLiveticker_System', 'activate' ) );
register_uninstall_hook( SCLIVETICKER_FILE, array( 'SCLiveticker_System', 'uninstall' ) );
// Allow shortcodes in widgets.
add_filter( 'widget_text', 'do_shortcode' );
// Add shortcode.
add_shortcode( 'liveticker', array( 'SCLiveticker\\SCLiveticker', 'shortcode_ticker_show' ) );
add_shortcode( 'liveticker', array( 'SCLiveticker', 'shortcode_ticker_show' ) );
// Add Widget.
add_action( 'widgets_init', array( 'SCLiveticker\\Widget', 'register' ) );
// Add Gutenberg block.
add_action( 'enqueue_block_editor_assets', array( 'SCLiveticker\\Admin', 'register_block' ) );
add_action( 'widgets_init', array( 'SCLiveticker_Widget', 'register' ) );
// Autoload.
spl_autoload_register( 'scliveticker_autoload' );
@ -64,24 +61,22 @@ spl_autoload_register( 'scliveticker_autoload' );
/**
* 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
*/
function scliveticker_autoload( string $class_name ): void {
function scliveticker_autoload( $class ) {
$plugin_classes = array(
'SCLiveticker\\SCLiveticker',
'SCLiveticker\\Admin',
'SCLiveticker\\Api',
'SCLiveticker\\Settings',
'SCLiveticker\\System',
'SCLiveticker\\Widget',
'SCLiveticker',
'SCLiveticker_Admin',
'SCLiveticker_System',
'SCLiveticker_Widget',
);
if ( in_array( $class_name, $plugin_classes, true ) ) {
if ( in_array( $class, $plugin_classes, true ) ) {
require_once sprintf(
'%s/includes/class-%s.php',
SCLIVETICKER_DIR,
strtolower( str_replace( '_', '-', substr( $class_name, 13 ) ) )
strtolower( str_replace( '_', '-', $class ) )
);
}
}

View File

@ -1,9 +0,0 @@
div.wp-block-scliveticker-ticker div.components-base-control:first-child label,
div.wp-block-scliveticker-ticker .label {
font-weight: 600;
}
div.wp-block-scliveticker-ticker .label > .dashicon,
div.wp-block-scliveticker-ticker div.components-base-control:first-child label > .dashicon {
margin-right: 8px;
}

View File

@ -1,10 +1,8 @@
div.wp-block-scliveticker-ticker ul,
div.wp-block-scliveticker-ajax-ticker ul {
ul.sclt-ticker {
list-style-type: none;
}
div.wp-block-scliveticker-ticker ul > li.sclt-tick,
div.wp-block-scliveticker-ajax-ticker ul > li.sclt-tick {
ul.sclt-ticker > li.sclt-tick {
background-color: #f5f5f5;
list-style-type: none;
margin: 0.1em;

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

@ -14,7 +14,7 @@ if ( ! defined( 'ABSPATH' ) ) {
<table>
<tr>
<td>
<label for="<?php echo esc_html( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:', 'stklcode-liveticker' ); ?></label>
<label for="<?php echo esc_html( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title:' ); ?></label>
</td>
<td>
<input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" />
@ -27,12 +27,12 @@ if ( ! defined( 'ABSPATH' ) ) {
<td>
<select id="<?php echo esc_attr( $this->get_field_id( 'category' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'category' ) ); ?>">
<?php
foreach ( $categories as $c ) {
echo '<option value="' . esc_attr( $c->slug ) . '"';
if ( $category === $c->slug ) {
foreach ( $categories as $cat ) {
echo '<option value="' . esc_attr( $cat->slug ) . '"';
if ( $category === $cat->slug ) {
echo ' selected="selected"';
}
echo '>' . esc_html( $c->name ) . '</option>';
echo '>' . esc_html( $cat->name ) . '</option>';
}
?>
</select>
@ -40,7 +40,7 @@ if ( ! defined( 'ABSPATH' ) ) {
</tr>
<tr>
<td>
<label for="<?php echo esc_attr( $this->get_field_id( 'count' ) ); ?>"><?php esc_html_e( 'Number of Ticks', 'stklcode-liveticker' ); ?>:</label>
<label for="<?php echo esc_attr( $this->get_field_id( 'count' ) ); ?>"><?php esc_html_e( 'Number of Ticks:', 'stklcode-liveticker' ); ?></label>
</td>
<td>
<select id="<?php echo esc_attr( $this->get_field_id( 'count' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'count' ) ); ?>">
@ -54,10 +54,10 @@ if ( ! defined( 'ABSPATH' ) ) {
<?php esc_html_e( 'all', 'stklcode-liveticker' ); ?>
</option>
<?php
for ( $i = 1; $i <= 10; $i++ ) {
for ( $i = 1; $i <= 10; $i ++ ) {
printf(
'<option value="%d"%s>%d</option>',
intval( $i ),
$i,
( $i === $count ) ? ' selected' : '',
intval( $i )
);