mirror of
https://ghproxy.net/https://github.com/fairpm/server.git
synced 2025-09-06 14:22:11 +08:00
Initial export of the repository
Signed-off-by: Ryan McCue <me@ryanmccue.info>
This commit is contained in:
commit
3e7bc6e67b
323 changed files with 28436 additions and 0 deletions
4
.build-script
Executable file
4
.build-script
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash -e
|
||||||
|
|
||||||
|
# Install composer dependencies
|
||||||
|
composer install --no-dev --prefer-dist --optimize-autoloader --apcu-autoloader
|
8
.config/constants.php
Normal file
8
.config/constants.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
define( 'HM_BASIC_AUTH_USER', 'openwebff' );
|
||||||
|
define( 'HM_BASIC_AUTH_PW', 'wither-nodding-dues-morals-splat' );
|
||||||
|
|
||||||
|
// GlotPress configuration.
|
||||||
|
define( 'GP_URL_BASE', '/' );
|
||||||
|
define( 'GP_TMPL_PATH', dirname( __DIR__ ) . '/content/plugins/f-translate/templates/' );
|
27
.config/env.php
Normal file
27
.config/env.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Connect environment variables to WP constants.
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined( 'DB_HOST' ) or define( 'DB_HOST', getenv( 'DB_HOST' ) );
|
||||||
|
defined( 'DB_USER' ) or define( 'DB_USER', getenv( 'DB_USER' ) );
|
||||||
|
defined( 'DB_PASSWORD' ) or define( 'DB_PASSWORD', getenv( 'DB_PASSWORD' ) );
|
||||||
|
defined( 'DB_NAME' ) or define( 'DB_NAME', getenv( 'DB_NAME' ) );
|
||||||
|
|
||||||
|
define( 'ELASTICSEARCH_HOST', getenv( 'ELASTICSEARCH_HOST' ) );
|
||||||
|
define( 'ELASTICSEARCH_PORT', getenv( 'ELASTICSEARCH_PORT' ) );
|
||||||
|
|
||||||
|
if ( ! defined( 'AWS_XRAY_DAEMON_IP_ADDRESS' ) ) {
|
||||||
|
define( 'AWS_XRAY_DAEMON_IP_ADDRESS', gethostbyname( getenv( 'AWS_XRAY_DAEMON_HOST' ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
define( 'REDIS_HOST', getenv( 'REDIS_HOST' ) );
|
||||||
|
define( 'REDIS_PORT', getenv( 'REDIS_PORT' ) );
|
||||||
|
define( 'REDIS_SECURE', false );
|
||||||
|
define( 'REDIS_AUTH', '' );
|
||||||
|
|
||||||
|
global $redis_server;
|
||||||
|
$redis_server = [
|
||||||
|
'host' => REDIS_HOST,
|
||||||
|
'port' => REDIS_PORT,
|
||||||
|
];
|
7
.config/load.php
Normal file
7
.config/load.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Load custom configuration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require __DIR__ . '/env.php';
|
||||||
|
require __DIR__ . '/constants.php';
|
12
.distignore
Normal file
12
.distignore
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
bin/
|
||||||
|
content/uploads/
|
||||||
|
gp-data/
|
||||||
|
node_modules/
|
||||||
|
vendor/
|
||||||
|
|
||||||
|
.distignore
|
||||||
|
.env.production
|
||||||
|
.gitignore
|
||||||
|
.secrets
|
25
.github/workflows/deploy.yml
vendored
Normal file
25
.github/workflows/deploy.yml
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Composer dependencies
|
||||||
|
uses: php-actions/composer@v6
|
||||||
|
|
||||||
|
- name: Run deploy script
|
||||||
|
run: bin/deploy.sh --apply
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
DEPLOY_HOST: ${{ secrets.PRODUCTION_DEPLOY_HOST }}
|
||||||
|
DEPLOY_USER: ${{ secrets.PRODUCTION_DEPLOY_USER }}
|
||||||
|
DEPLOY_PASS: ${{ secrets.PRODUCTION_DEPLOY_PASS }}
|
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
.env.production
|
||||||
|
gp-data/
|
||||||
|
|
||||||
|
# Installed via Composer
|
||||||
|
content/mu-plugins/php-basic-auth/
|
||||||
|
content/plugins/query-monitor/
|
||||||
|
content/plugins/wp-redis/
|
||||||
|
vendor/
|
||||||
|
wordpress/
|
||||||
|
|
17
.wp-env.json
Normal file
17
.wp-env.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"phpVersion": "8.2",
|
||||||
|
"multisite": true,
|
||||||
|
"mappings": {
|
||||||
|
".config": "./.config",
|
||||||
|
"wp-content": "./content",
|
||||||
|
"content": "./content"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"WP_DEBUG": true,
|
||||||
|
"WP_DEBUG_DISPLAY": true,
|
||||||
|
"WP_DEBUG_LOG": false
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"OllieJones/sqlite-object-cache"
|
||||||
|
]
|
||||||
|
}
|
38
README.md
Normal file
38
README.md
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Server
|
||||||
|
|
||||||
|
This is the overarching repo for the code deployed to our development server, running at https://beta.web-pub.org/
|
||||||
|
|
||||||
|
|
||||||
|
## Accessing
|
||||||
|
|
||||||
|
During development, the beta environment is protected with Basic authentication. The credentials are:
|
||||||
|
|
||||||
|
* **Username**: `openwebff`
|
||||||
|
* **Password**: `wither-nodding-dues-morals-splat`
|
||||||
|
|
||||||
|
|
||||||
|
## Local Development
|
||||||
|
|
||||||
|
This repository is pre-configured with wp-env configuration. You can set up a local environment using:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# First, install Composer dependencies.
|
||||||
|
composer install
|
||||||
|
|
||||||
|
# Then, start an environment.
|
||||||
|
npx @wordpress/env start
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Deployment will eventually be automatic, but in the meantime, you can run `bin/deploy.sh`. This will synchronize the `content/` directory to the server using rsync.
|
||||||
|
|
||||||
|
To ignore files from deployment, specify them in `.distignore`.
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Licensed under the GNU General Public License, v2 or later. Copyright 2025 contributors.
|
||||||
|
|
||||||
|
Incorporates code from [WordPress/wordpress.org](https://github.com/WordPress/wordpress.org). Licensed under the GNU General Public License, v2 or later. Copyright 2025 contributors.
|
40
bin/build.sh
Executable file
40
bin/build.sh
Executable file
|
@ -0,0 +1,40 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Build script that runs .build-script in a container and exports files
|
||||||
|
set -x
|
||||||
|
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
|
||||||
|
# Set up variables
|
||||||
|
BUILD_DIR="/tmp/fairserver-build"
|
||||||
|
CONTAINER_NAME="fair-builder"
|
||||||
|
|
||||||
|
# Clean up any existing build directory
|
||||||
|
rm -rf "$BUILD_DIR"
|
||||||
|
|
||||||
|
# Make a clean copy of the repo, using rsync
|
||||||
|
echo "Syncing to $BUILD_DIR…" >&2
|
||||||
|
rsync \
|
||||||
|
-a \
|
||||||
|
--progress \
|
||||||
|
--exclude='.git' \
|
||||||
|
--exclude-from='.distignore' \
|
||||||
|
"$SCRIPT_DIR/.." "$BUILD_DIR"
|
||||||
|
|
||||||
|
# Run the build script inside a composer container
|
||||||
|
echo "Running build script…" >&2
|
||||||
|
docker run \
|
||||||
|
--rm \
|
||||||
|
--name "$CONTAINER_NAME" \
|
||||||
|
-v "$BUILD_DIR:/app" \
|
||||||
|
-w /app \
|
||||||
|
composer:latest \
|
||||||
|
bash -c "./.build-script"
|
||||||
|
|
||||||
|
echo "Building image…" >&2
|
||||||
|
docker build \
|
||||||
|
--tag ghcr.io/fairpm/server:latest \
|
||||||
|
--build-context src="$BUILD_DIR" \
|
||||||
|
"$SCRIPT_DIR/container"
|
||||||
|
|
||||||
|
echo "Pushing to GitHub Container Registry…" >&2
|
||||||
|
docker push ghcr.io/fairpm/server:latest
|
56
bin/container/Dockerfile
Normal file
56
bin/container/Dockerfile
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
ARG ALPINE_VERSION=3.21
|
||||||
|
ARG SRC_DIR
|
||||||
|
FROM alpine:${ALPINE_VERSION}
|
||||||
|
|
||||||
|
# Setup document root
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install packages and remove default server definition
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
curl \
|
||||||
|
php83 \
|
||||||
|
php83-ctype \
|
||||||
|
php83-curl \
|
||||||
|
php83-dom \
|
||||||
|
php83-fileinfo \
|
||||||
|
php83-fpm \
|
||||||
|
php83-gd \
|
||||||
|
php83-intl \
|
||||||
|
php83-mbstring \
|
||||||
|
php83-mysqli \
|
||||||
|
php83-opcache \
|
||||||
|
php83-openssl \
|
||||||
|
php83-phar \
|
||||||
|
php83-session \
|
||||||
|
php83-tokenizer \
|
||||||
|
php83-xml \
|
||||||
|
php83-xmlreader \
|
||||||
|
php83-xmlwriter \
|
||||||
|
supervisor
|
||||||
|
|
||||||
|
# RUN ln -s /usr/bin/php83 /usr/bin/php
|
||||||
|
RUN ln -s /usr/sbin/php-fpm83 /usr/sbin/php-fpm
|
||||||
|
|
||||||
|
# Configure PHP-FPM
|
||||||
|
ENV PHP_INI_DIR=/etc/php83
|
||||||
|
COPY fpm-pool.conf ${PHP_INI_DIR}/php-fpm.d/www.conf
|
||||||
|
COPY php.ini ${PHP_INI_DIR}/conf.d/custom.ini
|
||||||
|
|
||||||
|
# Make sure files/folders needed by the processes are accessible when they run under the nobody user
|
||||||
|
RUN chown -R nobody:nobody /app /run
|
||||||
|
|
||||||
|
# Switch to use a non-root user from here on
|
||||||
|
USER nobody
|
||||||
|
|
||||||
|
# Add application
|
||||||
|
COPY --chown=nobody --from=src . /app/
|
||||||
|
|
||||||
|
# Expose the port nginx is reachable on
|
||||||
|
EXPOSE 9000
|
||||||
|
|
||||||
|
# Let supervisord start nginx & php-fpm
|
||||||
|
# CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||||
|
CMD ["php-fpm", "-F"]
|
||||||
|
|
||||||
|
# Configure a healthcheck to validate that everything is up&running
|
||||||
|
# HEALTHCHECK --timeout=10s CMD curl --silent --fail http://127.0.0.1:8080/fpm-ping || exit 1
|
43
bin/deploy.sh
Executable file
43
bin/deploy.sh
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
cd "$SCRIPT_DIR/.."
|
||||||
|
|
||||||
|
# If rsync is not installed, install it
|
||||||
|
if ! command -v rsync &> /dev/null; then
|
||||||
|
echo "Installing rsync"
|
||||||
|
apt update
|
||||||
|
apt install -y rsync
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if DEPLOY_USER, DEPLOY_HOST, and DEPLOY_PASS are set
|
||||||
|
if [[ -z "$DEPLOY_USER" || -z "$DEPLOY_HOST" || -z "$DEPLOY_PASS" ]]; then
|
||||||
|
echo "Error: DEPLOY_USER, DEPLOY_HOST, and DEPLOY_PASS must be set."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Force SSH to pull our password from the env var.
|
||||||
|
export SSH_ASKPASS='./bin/sshpass.sh'
|
||||||
|
export SSH_ASKPASS_REQUIRE='force'
|
||||||
|
|
||||||
|
# Check if --apply is passed as an argument
|
||||||
|
if [[ "$1" == "--apply" ]]; then
|
||||||
|
DRY_RUN=""
|
||||||
|
else
|
||||||
|
DRY_RUN="--dry-run"
|
||||||
|
fi
|
||||||
|
|
||||||
|
pwd
|
||||||
|
echo -n "Starting sync"
|
||||||
|
if [ -n "$DRY_RUN" ]; then
|
||||||
|
echo " (dry run)"
|
||||||
|
else
|
||||||
|
echo " (APPLYING)"
|
||||||
|
fi
|
||||||
|
rsync \
|
||||||
|
-e "ssh -o StrictHostKeyChecking=no" \
|
||||||
|
-avz \
|
||||||
|
--progress \
|
||||||
|
--delete $DRY_RUN \
|
||||||
|
--exclude-from='.distignore' \
|
||||||
|
./content/ $DEPLOY_USER@$DEPLOY_HOST:html/wp-content/
|
45
bin/import-gp.sh
Normal file
45
bin/import-gp.sh
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
cd "$SCRIPT_DIR/.."
|
||||||
|
|
||||||
|
SLUG="$1"
|
||||||
|
if [ -z "$SLUG" ]; then
|
||||||
|
echo "Usage: $0 <slug>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PROJECT_DIR="./gp-data/translations/$SLUG"
|
||||||
|
|
||||||
|
if [ ! -d "$PROJECT_DIR" ]; then
|
||||||
|
echo "Project directory not found: $PROJECT_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Import originals first.
|
||||||
|
echo "Importing originals (from en-gb)" >&2
|
||||||
|
if [ ! -f "$PROJECT_DIR/en-gb/default.po" ]; then
|
||||||
|
echo "Originals file not found: $PROJECT_DIR/en-gb/default.po"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
wp $WP_ARGS glotpress import-originals "$SLUG" "$PROJECT_DIR/en-gb/default.po"
|
||||||
|
|
||||||
|
for dir in "$PROJECT_DIR"/*; do
|
||||||
|
if [ ! -d "$dir" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
locale=$(basename "$dir")
|
||||||
|
echo "Importing translations for $locale" >&2
|
||||||
|
for file in "$dir"/*.po; do
|
||||||
|
if [ ! -f "$file" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
set="$(basename "$file" .po)"
|
||||||
|
echo " Importing $set" >&2
|
||||||
|
wp $WP_ARGS fair-translate translation-set ensure "$SLUG" "$locale" --set="$set"
|
||||||
|
wp $WP_ARGS glotpress translation-set import "$SLUG" "$locale" "$file" --set="$set"
|
||||||
|
done
|
||||||
|
done
|
24
bin/pull.sh
Executable file
24
bin/pull.sh
Executable file
|
@ -0,0 +1,24 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
cd $SCRIPT_DIR/..
|
||||||
|
|
||||||
|
# Force SSH to pull our password from the env var.
|
||||||
|
export SSH_ASKPASS='./bin/sshpass.sh'
|
||||||
|
export SSH_ASKPASS_REQUIRE='force'
|
||||||
|
|
||||||
|
# same as -rlptgoD
|
||||||
|
# -r = recursive
|
||||||
|
OPTS='-r'
|
||||||
|
# -L = follow links
|
||||||
|
OPTS+=' -L'
|
||||||
|
# preserve permissions, group, owner
|
||||||
|
OPTS+=' -pgo'
|
||||||
|
# -t = preserve modification times
|
||||||
|
OPTS+=' -t'
|
||||||
|
|
||||||
|
rsync \
|
||||||
|
-vz \
|
||||||
|
--exclude-from='.distignore' \
|
||||||
|
$OPTS \
|
||||||
|
$DEPLOY_USER@$DEPLOY_HOST:html/wp-content/ ./content/
|
2
bin/sshpass.sh
Executable file
2
bin/sshpass.sh
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/bash
|
||||||
|
echo "$DEPLOY_PASS"
|
33
composer.json
Normal file
33
composer.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "openwebff/server",
|
||||||
|
"type": "project",
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "vcs",
|
||||||
|
"url": "https://github.com/GlotPress/GlotPress"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"composer/installers": "~2",
|
||||||
|
"humanmade/php-basic-auth": "^1.1",
|
||||||
|
"glotpress/glotpress-wp": "^4.0.1",
|
||||||
|
"johnbillion/query-monitor": "^3.17",
|
||||||
|
"johnpbloch/wordpress": "^6.8",
|
||||||
|
"pantheon-systems/wp-redis": "^1.4",
|
||||||
|
"humanmade/wp-redis-predis-client": "^0.1.2",
|
||||||
|
"afragen/git-updater": "dev-fair@dev"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"installer-paths": {
|
||||||
|
"content/plugins/{$name}/": ["type:wordpress-plugin"],
|
||||||
|
"content/mu-plugins/{$name}/": ["type:wordpress-muplugin"],
|
||||||
|
"content/themes/{$name}/": ["type:wordpress-theme"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"allow-plugins": {
|
||||||
|
"composer/installers": true,
|
||||||
|
"johnpbloch/wordpress-core-installer": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
873
composer.lock
generated
Normal file
873
composer.lock
generated
Normal file
|
@ -0,0 +1,873 @@
|
||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "e75f9347be4ba8ada58a7950ad4fb199",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "afragen/git-updater",
|
||||||
|
"version": "dev-fair",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/afragen/git-updater.git",
|
||||||
|
"reference": "fae366b4a6f1b6a7a300b2fdfd5d40100c8863be"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/afragen/git-updater/zipball/fae366b4a6f1b6a7a300b2fdfd5d40100c8863be",
|
||||||
|
"reference": "fae366b4a6f1b6a7a300b2fdfd5d40100c8863be",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"afragen/singleton": "^1.0",
|
||||||
|
"afragen/wordpress-plugin-readme-parser": "^1",
|
||||||
|
"afragen/wp-dismiss-notice": "*",
|
||||||
|
"php": ">=8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"wp-coding-standards/wpcs": "^3.0.0"
|
||||||
|
},
|
||||||
|
"type": "wordpress-plugin",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Fragen\\Git_Updater\\": "src/Git_Updater/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andy Fragen",
|
||||||
|
"email": "andy@thefragens.com",
|
||||||
|
"homepage": "https://thefragens.com",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A plugin to automatically update GitHub, Bitbucket, GitLab, or Gitea hosted plugins, themes, and language packs.",
|
||||||
|
"keywords": [
|
||||||
|
"plugin",
|
||||||
|
"theme",
|
||||||
|
"updater",
|
||||||
|
"wordpress"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/afragen/git-updater/issues",
|
||||||
|
"source": "https://github.com/afragen/git-updater"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/afragen",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-06-03T15:27:28+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "afragen/singleton",
|
||||||
|
"version": "1.0.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/afragen/singleton.git",
|
||||||
|
"reference": "c93d90651dcf027bcd365d81bccf1eda73fe2f2d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/afragen/singleton/zipball/c93d90651dcf027bcd365d81bccf1eda73fe2f2d",
|
||||||
|
"reference": "c93d90651dcf027bcd365d81bccf1eda73fe2f2d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.6.5"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"classmap": [
|
||||||
|
"Singleton.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andy Fragen",
|
||||||
|
"email": "andy@thefragens.com",
|
||||||
|
"homepage": "https://github.com/afragen/singleton",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A singleton static proxy generator.",
|
||||||
|
"keywords": [
|
||||||
|
"singleton",
|
||||||
|
"wordpress"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/afragen/singleton/issues",
|
||||||
|
"source": "https://github.com/afragen/singleton/tree/1.0.5"
|
||||||
|
},
|
||||||
|
"time": "2024-11-19T17:40:31+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "afragen/wordpress-plugin-readme-parser",
|
||||||
|
"version": "1.2024.12.10",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/afragen/wordpress-plugin-readme-parser.git",
|
||||||
|
"reference": "803ccfc442d956aa04d69f4b234eda5d7f03afc4"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/afragen/wordpress-plugin-readme-parser/zipball/803ccfc442d956aa04d69f4b234eda5d7f03afc4",
|
||||||
|
"reference": "803ccfc442d956aa04d69f4b234eda5d7f03afc4",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"erusev/parsedown": "^1.7",
|
||||||
|
"php": ">=5.4"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"classmap": [
|
||||||
|
"class-parser.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "WordPress.org",
|
||||||
|
"homepage": "https://meta.trac.wordpress.org/browser/sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/readme"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A clone of the current WordPress.org Plugin Readme Parser, class-parser.php",
|
||||||
|
"keywords": [
|
||||||
|
"parser",
|
||||||
|
"readme",
|
||||||
|
"wordpress"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/afragen/wordpress-plugin-readme-parser/issues",
|
||||||
|
"source": "https://github.com/afragen/wordpress-plugin-readme-parser/tree/1.2024.12.10"
|
||||||
|
},
|
||||||
|
"time": "2024-12-10T23:43:17+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "afragen/wp-dismiss-notice",
|
||||||
|
"version": "0.3.7",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/afragen/wp-dismiss-notice.git",
|
||||||
|
"reference": "3e2c694ca891fe94771457f54a3a5457a70c0aec"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/afragen/wp-dismiss-notice/zipball/3e2c694ca891fe94771457f54a3a5457a70c0aec",
|
||||||
|
"reference": "3e2c694ca891fe94771457f54a3a5457a70c0aec",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.6"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"classmap": [
|
||||||
|
"wp-dismiss-notice.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andy Fragen",
|
||||||
|
"email": "andy@thefragens.com",
|
||||||
|
"homepage": "https://thefragens.com",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Library for time dismissible WordPress admin notices.",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/afragen/wp-dismiss-notice/issues",
|
||||||
|
"source": "https://github.com/afragen/wp-dismiss-notice"
|
||||||
|
},
|
||||||
|
"time": "2023-05-30T21:45:15+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "composer/installers",
|
||||||
|
"version": "v2.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/composer/installers.git",
|
||||||
|
"reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/composer/installers/zipball/12fb2dfe5e16183de69e784a7b84046c43d97e8e",
|
||||||
|
"reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"composer-plugin-api": "^1.0 || ^2.0",
|
||||||
|
"php": "^7.2 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"composer/composer": "^1.10.27 || ^2.7",
|
||||||
|
"composer/semver": "^1.7.2 || ^3.4.0",
|
||||||
|
"phpstan/phpstan": "^1.11",
|
||||||
|
"phpstan/phpstan-phpunit": "^1",
|
||||||
|
"symfony/phpunit-bridge": "^7.1.1",
|
||||||
|
"symfony/process": "^5 || ^6 || ^7"
|
||||||
|
},
|
||||||
|
"type": "composer-plugin",
|
||||||
|
"extra": {
|
||||||
|
"class": "Composer\\Installers\\Plugin",
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "2.x-dev"
|
||||||
|
},
|
||||||
|
"plugin-modifies-install-path": true
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Composer\\Installers\\": "src/Composer/Installers"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Kyle Robinson Young",
|
||||||
|
"email": "kyle@dontkry.com",
|
||||||
|
"homepage": "https://github.com/shama"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A multi-framework Composer library installer",
|
||||||
|
"homepage": "https://composer.github.io/installers/",
|
||||||
|
"keywords": [
|
||||||
|
"Dolibarr",
|
||||||
|
"Eliasis",
|
||||||
|
"Hurad",
|
||||||
|
"ImageCMS",
|
||||||
|
"Kanboard",
|
||||||
|
"Lan Management System",
|
||||||
|
"MODX Evo",
|
||||||
|
"MantisBT",
|
||||||
|
"Mautic",
|
||||||
|
"Maya",
|
||||||
|
"OXID",
|
||||||
|
"Plentymarkets",
|
||||||
|
"Porto",
|
||||||
|
"RadPHP",
|
||||||
|
"SMF",
|
||||||
|
"Starbug",
|
||||||
|
"Thelia",
|
||||||
|
"Whmcs",
|
||||||
|
"WolfCMS",
|
||||||
|
"agl",
|
||||||
|
"annotatecms",
|
||||||
|
"attogram",
|
||||||
|
"bitrix",
|
||||||
|
"cakephp",
|
||||||
|
"chef",
|
||||||
|
"cockpit",
|
||||||
|
"codeigniter",
|
||||||
|
"concrete5",
|
||||||
|
"concreteCMS",
|
||||||
|
"croogo",
|
||||||
|
"dokuwiki",
|
||||||
|
"drupal",
|
||||||
|
"eZ Platform",
|
||||||
|
"elgg",
|
||||||
|
"expressionengine",
|
||||||
|
"fuelphp",
|
||||||
|
"grav",
|
||||||
|
"installer",
|
||||||
|
"itop",
|
||||||
|
"known",
|
||||||
|
"kohana",
|
||||||
|
"laravel",
|
||||||
|
"lavalite",
|
||||||
|
"lithium",
|
||||||
|
"magento",
|
||||||
|
"majima",
|
||||||
|
"mako",
|
||||||
|
"matomo",
|
||||||
|
"mediawiki",
|
||||||
|
"miaoxing",
|
||||||
|
"modulework",
|
||||||
|
"modx",
|
||||||
|
"moodle",
|
||||||
|
"osclass",
|
||||||
|
"pantheon",
|
||||||
|
"phpbb",
|
||||||
|
"piwik",
|
||||||
|
"ppi",
|
||||||
|
"processwire",
|
||||||
|
"puppet",
|
||||||
|
"pxcms",
|
||||||
|
"reindex",
|
||||||
|
"roundcube",
|
||||||
|
"shopware",
|
||||||
|
"silverstripe",
|
||||||
|
"sydes",
|
||||||
|
"sylius",
|
||||||
|
"tastyigniter",
|
||||||
|
"wordpress",
|
||||||
|
"yawik",
|
||||||
|
"zend",
|
||||||
|
"zikula"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/composer/installers/issues",
|
||||||
|
"source": "https://github.com/composer/installers/tree/v2.3.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://packagist.com",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/composer",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-06-24T20:46:46+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "erusev/parsedown",
|
||||||
|
"version": "1.7.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/erusev/parsedown.git",
|
||||||
|
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
|
||||||
|
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^4.8.35"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"Parsedown": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Emanuil Rusev",
|
||||||
|
"email": "hello@erusev.com",
|
||||||
|
"homepage": "http://erusev.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Parser for Markdown.",
|
||||||
|
"homepage": "http://parsedown.org",
|
||||||
|
"keywords": [
|
||||||
|
"markdown",
|
||||||
|
"parser"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/erusev/parsedown/issues",
|
||||||
|
"source": "https://github.com/erusev/parsedown/tree/1.7.x"
|
||||||
|
},
|
||||||
|
"time": "2019-12-30T22:54:17+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "glotpress/glotpress-wp",
|
||||||
|
"version": "4.0.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/GlotPress/GlotPress.git",
|
||||||
|
"reference": "84cfcee8fec4de400469c901185fe1d65f7af797"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/GlotPress/GlotPress/zipball/84cfcee8fec4de400469c901185fe1d65f7af797",
|
||||||
|
"reference": "84cfcee8fec4de400469c901185fe1d65f7af797",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": "^1.0.0",
|
||||||
|
"gettext/languages": "^2.6",
|
||||||
|
"phpcompatibility/phpcompatibility-wp": "^2.1",
|
||||||
|
"phpunit/phpunit": "^9.6.15",
|
||||||
|
"wp-coding-standards/wpcs": "^2.2",
|
||||||
|
"yoast/phpunit-polyfills": "^2.0"
|
||||||
|
},
|
||||||
|
"type": "wordpress-plugin",
|
||||||
|
"scripts": {
|
||||||
|
"format": [
|
||||||
|
"@php ./vendor/bin/phpcbf --report=summary,source"
|
||||||
|
],
|
||||||
|
"lint": [
|
||||||
|
"@php ./vendor/bin/phpcs --report=summary,source"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"@php ./vendor/bin/phpunit"
|
||||||
|
],
|
||||||
|
"test:locales": [
|
||||||
|
"@test --group locales"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"license": [
|
||||||
|
"GPL-2.0-or-later"
|
||||||
|
],
|
||||||
|
"description": "GlotPress is a collaborative, web-based software translation tool.",
|
||||||
|
"homepage": "https://glotpress.blog/",
|
||||||
|
"keywords": [
|
||||||
|
"i18n",
|
||||||
|
"translations",
|
||||||
|
"wordpress"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/GlotPress/GlotPress/issues",
|
||||||
|
"forum": "https://wordpress.org/support/plugin/glotpress/",
|
||||||
|
"source": "https://github.com/GlotPress/GlotPress",
|
||||||
|
"docs": "https://glotpress.blog/the-manual/"
|
||||||
|
},
|
||||||
|
"time": "2024-04-03T14:29:59+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "humanmade/php-basic-auth",
|
||||||
|
"version": "1.1.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/humanmade/PHPBasicAuth.git",
|
||||||
|
"reference": "83e598d79df68054dc7b90cb171faaf95b601883"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/humanmade/PHPBasicAuth/zipball/83e598d79df68054dc7b90cb171faaf95b601883",
|
||||||
|
"reference": "83e598d79df68054dc7b90cb171faaf95b601883",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"humanmade/coding-standards": "^0.7.0",
|
||||||
|
"phpunit/phpunit": "^7.5"
|
||||||
|
},
|
||||||
|
"type": "wordpress-muplugin",
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"GPL-3.0-or-later"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Chris Reynolds",
|
||||||
|
"email": "chris@humanmade.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Basic PHP authentication for HM Dev and Staging environments.",
|
||||||
|
"homepage": "https://github.com/humanmade/PHPBasicAuth",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/humanmade/PHPBasicAuth/issues",
|
||||||
|
"source": "https://github.com/humanmade/PHPBasicAuth/tree/1.1.1"
|
||||||
|
},
|
||||||
|
"time": "2020-01-10T21:55:59+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "humanmade/wp-redis-predis-client",
|
||||||
|
"version": "0.1.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/humanmade/wp-redis-predis-client.git",
|
||||||
|
"reference": "4fef0721054a756f3f2300c2227f565acea391bd"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/humanmade/wp-redis-predis-client/zipball/4fef0721054a756f3f2300c2227f565acea391bd",
|
||||||
|
"reference": "4fef0721054a756f3f2300c2227f565acea391bd",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"predis/predis": "~1.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^7.1.4"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"functions.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/humanmade/wp-redis-predis-client/issues",
|
||||||
|
"source": "https://github.com/humanmade/wp-redis-predis-client/tree/0.1.2"
|
||||||
|
},
|
||||||
|
"time": "2025-01-24T17:01:53+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "johnbillion/query-monitor",
|
||||||
|
"version": "3.17.2",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/johnbillion/query-monitor.git",
|
||||||
|
"reference": "e35dbc8ac937178d214c04552a0a802dacf6c79b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/johnbillion/query-monitor/zipball/e35dbc8ac937178d214c04552a0a802dacf6c79b",
|
||||||
|
"reference": "e35dbc8ac937178d214c04552a0a802dacf6c79b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"composer/installers": "^1.0 || ^2.0",
|
||||||
|
"php": ">=7.4.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"codeception/module-asserts": "^1.0",
|
||||||
|
"codeception/module-db": "^1.0",
|
||||||
|
"codeception/module-webdriver": "^1.0",
|
||||||
|
"codeception/util-universalframework": "^1.0",
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": "0.7.2",
|
||||||
|
"johnbillion/plugin-infrastructure": "dev-trunk",
|
||||||
|
"johnbillion/wp-compat": "0.3.1",
|
||||||
|
"lucatume/wp-browser": "3.2.3",
|
||||||
|
"phpcompatibility/phpcompatibility-wp": "2.1.5",
|
||||||
|
"phpstan/phpstan": "1.12.11",
|
||||||
|
"phpstan/phpstan-deprecation-rules": "1.2.1",
|
||||||
|
"phpstan/phpstan-phpunit": "1.4.1",
|
||||||
|
"roots/wordpress-core-installer": "1.100.0",
|
||||||
|
"roots/wordpress-full": "*",
|
||||||
|
"squizlabs/php_codesniffer": "3.11.1",
|
||||||
|
"szepeviktor/phpstan-wordpress": "1.3.5",
|
||||||
|
"wp-coding-standards/wpcs": "2.3.0"
|
||||||
|
},
|
||||||
|
"type": "wordpress-plugin",
|
||||||
|
"extra": {
|
||||||
|
"wordpress-install-dir": "vendor/wordpress/wordpress"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"classmap": [
|
||||||
|
"classes",
|
||||||
|
"data",
|
||||||
|
"output"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"GPL-2.0-or-later"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "John Blackbourn",
|
||||||
|
"homepage": "https://johnblackbourn.com/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "The Developer Tools panel for WordPress.",
|
||||||
|
"homepage": "https://querymonitor.com/",
|
||||||
|
"support": {
|
||||||
|
"forum": "https://wordpress.org/support/plugin/query-monitor",
|
||||||
|
"issues": "https://github.com/johnbillion/query-monitor/issues",
|
||||||
|
"security": "https://patchstack.com/database/vdp/query-monitor",
|
||||||
|
"source": "https://github.com/johnbillion/query-monitor"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/sponsors/johnbillion",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-02-04T22:13:08+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "johnpbloch/wordpress",
|
||||||
|
"version": "6.8.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/johnpbloch/wordpress.git",
|
||||||
|
"reference": "1932f5ab26a420151c689a9c00505c70c4f91ab3"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/johnpbloch/wordpress/zipball/1932f5ab26a420151c689a9c00505c70c4f91ab3",
|
||||||
|
"reference": "1932f5ab26a420151c689a9c00505c70c4f91ab3",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"johnpbloch/wordpress-core": "6.8.1",
|
||||||
|
"johnpbloch/wordpress-core-installer": "^1.0 || ^2.0",
|
||||||
|
"php": ">=7.0.0"
|
||||||
|
},
|
||||||
|
"type": "package",
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"GPL-2.0-or-later"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "WordPress Community",
|
||||||
|
"homepage": "https://wordpress.org/about/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "WordPress is open source software you can use to create a beautiful website, blog, or app.",
|
||||||
|
"homepage": "https://wordpress.org/",
|
||||||
|
"keywords": [
|
||||||
|
"blog",
|
||||||
|
"cms",
|
||||||
|
"wordpress"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"docs": "https://developer.wordpress.org/",
|
||||||
|
"forum": "https://wordpress.org/support/",
|
||||||
|
"irc": "irc://irc.freenode.net/wordpress",
|
||||||
|
"issues": "https://core.trac.wordpress.org/",
|
||||||
|
"source": "https://core.trac.wordpress.org/browser"
|
||||||
|
},
|
||||||
|
"time": "2025-04-30T16:52:55+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "johnpbloch/wordpress-core",
|
||||||
|
"version": "6.8.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/johnpbloch/wordpress-core.git",
|
||||||
|
"reference": "702c3bda87abb47ae43f90c7b8868cf0b7dff1e2"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/johnpbloch/wordpress-core/zipball/702c3bda87abb47ae43f90c7b8868cf0b7dff1e2",
|
||||||
|
"reference": "702c3bda87abb47ae43f90c7b8868cf0b7dff1e2",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"php": ">=7.2.24"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"wordpress/core-implementation": "6.8.1"
|
||||||
|
},
|
||||||
|
"type": "wordpress-core",
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"GPL-2.0-or-later"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "WordPress Community",
|
||||||
|
"homepage": "https://wordpress.org/about/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "WordPress is open source software you can use to create a beautiful website, blog, or app.",
|
||||||
|
"homepage": "https://wordpress.org/",
|
||||||
|
"keywords": [
|
||||||
|
"blog",
|
||||||
|
"cms",
|
||||||
|
"wordpress"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"forum": "https://wordpress.org/support/",
|
||||||
|
"irc": "irc://irc.freenode.net/wordpress",
|
||||||
|
"issues": "https://core.trac.wordpress.org/",
|
||||||
|
"source": "https://core.trac.wordpress.org/browser",
|
||||||
|
"wiki": "https://codex.wordpress.org/"
|
||||||
|
},
|
||||||
|
"time": "2025-04-30T16:52:49+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "johnpbloch/wordpress-core-installer",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/johnpbloch/wordpress-core-installer.git",
|
||||||
|
"reference": "237faae9a60a4a2e1d45dce1a5836ffa616de63e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/johnpbloch/wordpress-core-installer/zipball/237faae9a60a4a2e1d45dce1a5836ffa616de63e",
|
||||||
|
"reference": "237faae9a60a4a2e1d45dce1a5836ffa616de63e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"composer-plugin-api": "^1.0 || ^2.0",
|
||||||
|
"php": ">=5.6.0"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"composer/installers": "<1.0.6"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"composer/composer": "^1.0 || ^2.0",
|
||||||
|
"phpunit/phpunit": ">=5.7.27"
|
||||||
|
},
|
||||||
|
"type": "composer-plugin",
|
||||||
|
"extra": {
|
||||||
|
"class": "johnpbloch\\Composer\\WordPressCorePlugin"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"johnpbloch\\Composer\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"GPL-2.0-or-later"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "John P. Bloch",
|
||||||
|
"email": "me@johnpbloch.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A custom installer to handle deploying WordPress with composer",
|
||||||
|
"keywords": [
|
||||||
|
"wordpress"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/johnpbloch/wordpress-core-installer/issues",
|
||||||
|
"source": "https://github.com/johnpbloch/wordpress-core-installer/tree/master"
|
||||||
|
},
|
||||||
|
"time": "2020-04-16T21:44:57+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pantheon-systems/wp-redis",
|
||||||
|
"version": "1.4.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/pantheon-systems/wp-redis.git",
|
||||||
|
"reference": "034f87e96fb778c0cea6c0407ecdd41a44661dc9"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/pantheon-systems/wp-redis/zipball/034f87e96fb778c0cea6c0407ecdd41a44661dc9",
|
||||||
|
"reference": "034f87e96fb778c0cea6c0407ecdd41a44661dc9",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"behat/behat": "^3.1",
|
||||||
|
"behat/mink-extension": "^2.2",
|
||||||
|
"behat/mink-goutte-driver": "^1.2",
|
||||||
|
"pantheon-systems/pantheon-wordpress-upstream-tests": "dev-master",
|
||||||
|
"pantheon-systems/pantheon-wp-coding-standards": "^2.0",
|
||||||
|
"pantheon-systems/wpunit-helpers": "^2.0",
|
||||||
|
"phpunit/phpunit": "^9",
|
||||||
|
"yoast/phpunit-polyfills": "^3.0"
|
||||||
|
},
|
||||||
|
"type": "wordpress-plugin",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"behat\\features\\bootstrap\\": "tests/behat/features/bootstrap/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"GPL-2.0-or-later"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Pantheon",
|
||||||
|
"email": "noreply@pantheon.io"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/pantheon-systems/wp-redis/issues",
|
||||||
|
"source": "https://github.com/pantheon-systems/wp-redis/tree/1.4.5"
|
||||||
|
},
|
||||||
|
"time": "2025-01-21T18:55:01+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "predis/predis",
|
||||||
|
"version": "v1.1.10",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/predis/predis.git",
|
||||||
|
"reference": "a2fb02d738bedadcffdbb07efa3a5e7bd57f8d6e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/predis/predis/zipball/a2fb02d738bedadcffdbb07efa3a5e7bd57f8d6e",
|
||||||
|
"reference": "a2fb02d738bedadcffdbb07efa3a5e7bd57f8d6e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.9"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "~4.8"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-curl": "Allows access to Webdis when paired with phpiredis",
|
||||||
|
"ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Predis\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Daniele Alessandri",
|
||||||
|
"email": "suppakilla@gmail.com",
|
||||||
|
"homepage": "http://clorophilla.net",
|
||||||
|
"role": "Creator & Maintainer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Till Krüss",
|
||||||
|
"homepage": "https://till.im",
|
||||||
|
"role": "Maintainer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Flexible and feature-complete Redis client for PHP and HHVM",
|
||||||
|
"homepage": "http://github.com/predis/predis",
|
||||||
|
"keywords": [
|
||||||
|
"nosql",
|
||||||
|
"predis",
|
||||||
|
"redis"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/predis/predis/issues",
|
||||||
|
"source": "https://github.com/predis/predis/tree/v1.1.10"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/sponsors/tillkruss",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2022-01-05T17:46:08+00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": {
|
||||||
|
"afragen/git-updater": 20
|
||||||
|
},
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": {},
|
||||||
|
"platform-dev": {},
|
||||||
|
"plugin-api-version": "2.6.0"
|
||||||
|
}
|
42
content/mu-plugins/10-autoloader.php
Normal file
42
content/mu-plugins/10-autoloader.php
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FAIRServer;
|
||||||
|
|
||||||
|
const NS_SEPARATOR = '\\';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a path for autoloading.
|
||||||
|
*
|
||||||
|
* @param string $prefix The namespace prefix.
|
||||||
|
* @param string $path The path to the class files.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
function register_class_path( string $prefix, string $path ) : void {
|
||||||
|
$prefix_length = strlen( $prefix );
|
||||||
|
spl_autoload_register( function ( $class ) use ( $prefix, $prefix_length, $path ) {
|
||||||
|
if ( strpos( $class, $prefix . NS_SEPARATOR ) !== 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip prefix from the start (ala PSR-4).
|
||||||
|
$class = substr( $class, $prefix_length + 1 );
|
||||||
|
$class = strtolower( $class );
|
||||||
|
$class = str_replace( '_', '-', $class );
|
||||||
|
$file = '';
|
||||||
|
|
||||||
|
// Split on namespace separator.
|
||||||
|
$last_ns_pos = strripos( $class, NS_SEPARATOR );
|
||||||
|
if ( $last_ns_pos !== false ) {
|
||||||
|
$namespace = substr( $class, 0, $last_ns_pos );
|
||||||
|
$class = substr( $class, $last_ns_pos + 1 );
|
||||||
|
$file = str_replace( NS_SEPARATOR, DIRECTORY_SEPARATOR, $namespace ) . DIRECTORY_SEPARATOR;
|
||||||
|
}
|
||||||
|
$file .= 'class-' . $class . '.php';
|
||||||
|
|
||||||
|
$path = $path . $file;
|
||||||
|
|
||||||
|
if ( file_exists( $path ) ) {
|
||||||
|
require_once $path;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
3
content/object-cache.php
Normal file
3
content/object-cache.php
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<?php
|
||||||
|
WP_Predis\add_filters();
|
||||||
|
require_once __DIR__ . '/plugins/wp-redis/object-cache.php';
|
95
content/plugins/airplane-mode/CHANGES.md
Normal file
95
content/plugins/airplane-mode/CHANGES.md
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
#### Version 0.2.5 - 2019/11/15
|
||||||
|
* Remove usage of method that's been deprecated in WordPress trunk. props @johnbillion
|
||||||
|
|
||||||
|
#### Version 0.2.4 - 2017/12/13
|
||||||
|
* Added localhost bypass for loading CSS and JS files when hot reloading. props @shadyvb
|
||||||
|
* Added additional hook removals for new automated updates and language packs.
|
||||||
|
* Removed outdated `create_function` call for PHP 7.2 compatibility. props @geminorum
|
||||||
|
* General cleanup
|
||||||
|
|
||||||
|
#### Version 0.2.3 - 2017/03/05
|
||||||
|
* Adding option to force transient purge
|
||||||
|
* Adding `clean` action to WP-CLI functions
|
||||||
|
|
||||||
|
#### Version 0.2.2 - 2016/12/07
|
||||||
|
* Adding WP-CLI support. props @markjaquith
|
||||||
|
|
||||||
|
#### Version 0.2.1 - 2016/10/19
|
||||||
|
* Fix settings page language drowdown when used offline. props @onnimonni
|
||||||
|
|
||||||
|
#### Version 0.2.0 - 2016/08/26
|
||||||
|
* modify CSS loading for front-end, removed loading for login page. props @barryceelen
|
||||||
|
* fixed WP.org theme API call request to avoid `WP_Error` return. props @onnimonni
|
||||||
|
|
||||||
|
#### Version 0.1.9 - 2016/07/25
|
||||||
|
* prevent BuddyPress from falling back to Gravatar. props @johnbillion
|
||||||
|
|
||||||
|
#### Version 0.1.8 - 2016/07/12
|
||||||
|
* allow `JETPACK_DEV_DEBUG` constant to take priority over filter. props @kopepasah
|
||||||
|
* added additional CSS for upcoming 4.6. change to upload tab.
|
||||||
|
|
||||||
|
#### Version 0.1.7 - 2016/05/18
|
||||||
|
* allow local HTTP calls with optional filter. props @johnbillion
|
||||||
|
* add back index.php link to main dashboard menu item
|
||||||
|
* bumped minimum WP version requirement to 4.4
|
||||||
|
|
||||||
|
#### Version 0.1.6 - 2016/04/25
|
||||||
|
* minor tweak to include CSS for new icon font
|
||||||
|
|
||||||
|
#### Version 0.1.5 - 2016/04/24
|
||||||
|
* adding custom icon font for display and removing label. props @barryceelen
|
||||||
|
|
||||||
|
#### Version 0.1.4 - 2016/02/26
|
||||||
|
* better setup for blocked external assets. props @johnbillion
|
||||||
|
|
||||||
|
#### Version 0.1.3 - 2016/02/22
|
||||||
|
* modified CSS rules to fix media bulk actions bar from disappearing
|
||||||
|
* moved `airplane_mode_status_change` action to run before redirect, and now includes the status being run.
|
||||||
|
|
||||||
|
#### Version 0.1.2 - 2016/01/09
|
||||||
|
* added back HTTP count when inactive
|
||||||
|
* removed HTTP count completely when Query Monitor is active
|
||||||
|
|
||||||
|
#### Version 0.1.1 - 2016/01/06
|
||||||
|
* fixed incorrect nonce check that was breaking toggle
|
||||||
|
* changed CSS and JS checks to include all themes and plugins as well as core
|
||||||
|
|
||||||
|
#### Version 0.1.0 - 2015/12/30
|
||||||
|
* added `airplane_mode_purge_transients` filter to bypass transient purge
|
||||||
|
|
||||||
|
#### Version 0.0.9 - 2015/12/07
|
||||||
|
* changed from colored circle to actual airplane icon for usability
|
||||||
|
* fixed dashboard link icon for multisite
|
||||||
|
* changed to exclude all external stylesheets, not just Open Sans
|
||||||
|
* added language files for translateable goodness
|
||||||
|
* general cleanup for WP coding standards
|
||||||
|
|
||||||
|
#### Version 0.0.8 - 2015/05/18
|
||||||
|
* added `class_exists` as now included in DesktopServer and collisions could result
|
||||||
|
* fixed `if ( ! defined ...` for `AIRMDE_BASE` constant
|
||||||
|
* add `.gitattributes` to remove certain files from updates
|
||||||
|
|
||||||
|
#### Version 0.0.7 - 2015/04/21
|
||||||
|
* fixed some CSS from hiding plugins page bar
|
||||||
|
* moved changelog to it's own file
|
||||||
|
* added `composer.json`
|
||||||
|
* added contributors to readme
|
||||||
|
* clarified license (MIT)
|
||||||
|
|
||||||
|
#### Version 0.0.6 - 2015/04/02
|
||||||
|
* version bump for GitHub updater
|
||||||
|
|
||||||
|
#### Version 0.0.5 - 2015/04/02
|
||||||
|
* fixed bug in update logic that was preventing checks when disabled (but activated). props @johnbillion
|
||||||
|
|
||||||
|
#### Version 0.0.3 - 2015/01/23
|
||||||
|
* added `airplane_mode_status_change` hook for functions to fire on status change
|
||||||
|
* purge update related transients on disable
|
||||||
|
* added WordPress formatted readme file
|
||||||
|
|
||||||
|
#### Version 0.0.2 - 2015/01/21
|
||||||
|
* added GitHub Updater support
|
||||||
|
* fixed update capabilities when status is disabled
|
||||||
|
|
||||||
|
#### Version 0.0.1 - 2014/06/01
|
||||||
|
* lots of stuff. I wasn't keeping a changelog. I apologize.
|
21
content/plugins/airplane-mode/LICENSE
Normal file
21
content/plugins/airplane-mode/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Norcross
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
34
content/plugins/airplane-mode/README.md
Normal file
34
content/plugins/airplane-mode/README.md
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
Airplane Mode
|
||||||
|
========================
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
* [Andrew Norcross](https://github.com/norcross)
|
||||||
|
* [John Blackbourn](https://github.com/johnbillion)
|
||||||
|
* [Andy Fragen](https://github.com/afragen)
|
||||||
|
* [Viktor Szépe](https://github.com/szepeviktor)
|
||||||
|
* [Chris Christoff](https://github.com/chriscct7)
|
||||||
|
* [Mark Jaquith](https://github.com/markjaquith)
|
||||||
|
|
||||||
|
## About
|
||||||
|
Control loading of external files when developing locally. WP loads certain external files (fonts, Gravatar, etc.) and makes external HTTP calls. This isn't usually an issue, unless you're working in an evironment without a web connection. This plugin removes/unhooks those actions to reduce load time and avoid errors due to missing files.
|
||||||
|
|
||||||
|
## Current Actions
|
||||||
|
* removes external JS and CSS files from loading
|
||||||
|
* replaces all instances of Gravatar with a local image to remove external call
|
||||||
|
* removes all HTTP requests
|
||||||
|
* disables all WP update checks for core, languages, themes, and plugins
|
||||||
|
* includes toggle in admin bar for quick enable / disable
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
See [CHANGES.md](CHANGES.md).
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
If you need offline activation, see [this script](https://gist.github.com/solepixel/e1d03f4dcd1b9e86552b3cc6937325bf) written by [Brian DiChiara](https://github.com/solepixel)
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
* fine tune HTTP request removal
|
||||||
|
* find other calls from core
|
||||||
|
* add other requests from popular plugins
|
||||||
|
|
||||||
|
#### [Pull requests](https://github.com/norcross/airplane-mode/pulls) are very much welcome and encouraged.
|
1288
content/plugins/airplane-mode/airplane-mode.php
Normal file
1288
content/plugins/airplane-mode/airplane-mode.php
Normal file
File diff suppressed because it is too large
Load diff
66
content/plugins/airplane-mode/inc/wp-cli.php
Normal file
66
content/plugins/airplane-mode/inc/wp-cli.php
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Airplane_Mode_Command extends WP_CLI_Command {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables airplane mode.
|
||||||
|
*
|
||||||
|
* ## EXAMPLES
|
||||||
|
*
|
||||||
|
* wp airplane-mode enable
|
||||||
|
*
|
||||||
|
* @when after_wp_load
|
||||||
|
* @subcommand on
|
||||||
|
* @alias enable
|
||||||
|
*/
|
||||||
|
function enable() {
|
||||||
|
Airplane_Mode_Core::getInstance()->enable();
|
||||||
|
WP_CLI::success( __( 'Airplane mode was enabled', 'airplane-mode' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables airplane mode.
|
||||||
|
*
|
||||||
|
* ## EXAMPLES
|
||||||
|
*
|
||||||
|
* wp airplane-mode disable
|
||||||
|
*
|
||||||
|
* @when after_wp_load
|
||||||
|
* @subcommand off
|
||||||
|
* @alias disable
|
||||||
|
*/
|
||||||
|
function disable() {
|
||||||
|
Airplane_Mode_Core::getInstance()->disable();
|
||||||
|
WP_CLI::success( __( 'Airplane mode was disabled', 'airplane-mode' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the status of airplane mode.
|
||||||
|
*
|
||||||
|
* ## EXAMPLES
|
||||||
|
*
|
||||||
|
* wp airplane-mode status
|
||||||
|
*
|
||||||
|
* @when after_wp_load
|
||||||
|
*/
|
||||||
|
function status() {
|
||||||
|
$on = 'on' === get_site_option( 'airplane-mode' );
|
||||||
|
WP_CLI::success( $on ? __( 'Airplane mode is enabled', 'airplane-mode' ) : __( 'Airplane mode is disabled', 'airplane-mode' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purge the transients set from airplane mode.
|
||||||
|
*
|
||||||
|
* ## EXAMPLES
|
||||||
|
*
|
||||||
|
* wp airplane-mode clean
|
||||||
|
*
|
||||||
|
* @when after_wp_load
|
||||||
|
*/
|
||||||
|
function clean() {
|
||||||
|
Airplane_Mode_Core::getInstance()->purge_transients( true );
|
||||||
|
WP_CLI::success( __( 'Transients have been cleared', 'airplane-mode' ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WP_CLI::add_command( 'airplane-mode', 'Airplane_Mode_Command' );
|
BIN
content/plugins/airplane-mode/languages/default.mo
Normal file
BIN
content/plugins/airplane-mode/languages/default.mo
Normal file
Binary file not shown.
47
content/plugins/airplane-mode/languages/default.po
Normal file
47
content/plugins/airplane-mode/languages/default.po
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Airplane Mode\n"
|
||||||
|
"POT-Creation-Date: 2015-12-07 17:24-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-12-07 17:25-0500\n"
|
||||||
|
"Last-Translator: Andrew Norcross <andrew.norcross@gmail.com>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: en_US\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"X-Generator: Poedit 1.8.6\n"
|
||||||
|
"X-Poedit-Basepath: ..\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Poedit-SourceCharset: UTF-8\n"
|
||||||
|
"X-Poedit-KeywordsList: _;__;_e;_n\n"
|
||||||
|
"X-Poedit-SearchPath-0: .\n"
|
||||||
|
|
||||||
|
#: airplane-mode.php:365
|
||||||
|
#, php-format
|
||||||
|
msgid "Airplane Mode is enabled. oEmbed blocked for %1$s."
|
||||||
|
msgstr "Airplane Mode is enabled. oEmbed blocked for %1$s."
|
||||||
|
|
||||||
|
#: airplane-mode.php:475
|
||||||
|
msgid "Airplane Mode is enabled"
|
||||||
|
msgstr "Airplane Mode is enabled"
|
||||||
|
|
||||||
|
#: airplane-mode.php:583
|
||||||
|
msgid "Airplane Mode is enabled."
|
||||||
|
msgstr "Airplane Mode is enabled."
|
||||||
|
|
||||||
|
#: airplane-mode.php:583
|
||||||
|
msgid "Airplane Mode is disabled."
|
||||||
|
msgstr "Airplane Mode is disabled."
|
||||||
|
|
||||||
|
#: airplane-mode.php:592
|
||||||
|
msgid "Active"
|
||||||
|
msgstr "Active"
|
||||||
|
|
||||||
|
#: airplane-mode.php:592
|
||||||
|
msgid "Inactive"
|
||||||
|
msgstr "Inactive"
|
||||||
|
|
||||||
|
#: airplane-mode.php:609
|
||||||
|
#, php-format
|
||||||
|
msgid "There was %s HTTP request."
|
||||||
|
msgstr "There was %s HTTP request."
|
59
content/plugins/airplane-mode/lib/css/airplane-mode.css
Normal file
59
content/plugins/airplane-mode/lib/css/airplane-mode.css
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: "airplanemodeicons";
|
||||||
|
src:url("../fonts/airplanemodeicons.eot");
|
||||||
|
src:url("../fonts/airplanemodeicons.eot?#iefix") format("embedded-opentype"),
|
||||||
|
url("../fonts/airplanemodeicons.woff") format("woff"),
|
||||||
|
url("../fonts/airplanemodeicons.ttf") format("truetype"),
|
||||||
|
url("../fonts/airplanemodeicons.svg#airplanemodeicons") format("svg");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wp-admin-bar-airplane-mode-toggle .ab-icon {
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wp-admin-bar-airplane-mode-toggle .ab-icon:before {
|
||||||
|
content: "\e000";
|
||||||
|
font: normal 18px/1 airplanemodeicons;
|
||||||
|
}
|
||||||
|
|
||||||
|
.airplane-mode-disabled #wp-admin-bar-airplane-mode-toggle .ab-icon:before {
|
||||||
|
content: "\e001";
|
||||||
|
}
|
||||||
|
|
||||||
|
#wpadminbar > #wp-toolbar > #wp-admin-bar-root-default > #wp-admin-bar-airplane-mode-toggle .ab-icon,
|
||||||
|
#wpadminbar #wp-admin-bar-airplane-mode-toggle .ab-icon,
|
||||||
|
#wpadminbar #wp-admin-bar-airplane-mode-toggle .ab-item::before {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.airplane-mode-no-qm #wpadminbar > #wp-toolbar > #wp-admin-bar-root-default > #wp-admin-bar-airplane-mode-toggle .ab-icon,
|
||||||
|
.airplane-mode-no-qm #wpadminbar #wp-admin-bar-airplane-mode-toggle .ab-icon,
|
||||||
|
.airplane-mode-no-qm #wpadminbar #wp-admin-bar-airplane-mode-toggle .ab-item::before {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the opacity to mimic comment moderation queue. */
|
||||||
|
.airplane-mode-disabled #wp-admin-bar-airplane-mode-toggle .ab-label {
|
||||||
|
/* Theoretically for IE 8 & 9 (more valid) */
|
||||||
|
/* ...but not required as filter works too */
|
||||||
|
/* should come BEFORE filter */
|
||||||
|
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
|
||||||
|
|
||||||
|
/* This works in IE 8 & 9 too */
|
||||||
|
/* ... but also 5, 6, 7 */
|
||||||
|
filter: alpha(opacity=50); /* IE 5-7 */
|
||||||
|
|
||||||
|
/* Modern Browsers */
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.airplane-mode-enabled.plugin-install-php.branch-4-6 h1 a.upload-view-toggle,
|
||||||
|
.airplane-mode-enabled.plugin-install-php h1 a.upload,
|
||||||
|
.airplane-mode-enabled.plugin-install-php .wp-filter,
|
||||||
|
.airplane-mode-enabled.theme-install-php .wp-filter,
|
||||||
|
.airplane-mode-enabled .theme-browser.content-filterable.rendered,
|
||||||
|
.airplane-mode-enabled.theme-install-php a.browse-themes {
|
||||||
|
display: none !important;
|
||||||
|
}
|
1
content/plugins/airplane-mode/lib/css/airplane-mode.min.css
vendored
Normal file
1
content/plugins/airplane-mode/lib/css/airplane-mode.min.css
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
@font-face{font-family:airplanemodeicons;src:url(../fonts/airplanemodeicons.eot);src:url(../fonts/airplanemodeicons.eot?#iefix) format("embedded-opentype"),url(../fonts/airplanemodeicons.woff) format("woff"),url(../fonts/airplanemodeicons.ttf) format("truetype"),url(../fonts/airplanemodeicons.svg#airplanemodeicons) format("svg");font-weight:400;font-style:normal}#wp-admin-bar-airplane-mode-toggle .ab-icon{top:1px}#wp-admin-bar-airplane-mode-toggle .ab-icon:before{content:"\e000";font:400 18px/1 airplanemodeicons}.airplane-mode-disabled #wp-admin-bar-airplane-mode-toggle .ab-icon:before{content:"\e001"}#wpadminbar #wp-admin-bar-airplane-mode-toggle .ab-icon,#wpadminbar #wp-admin-bar-airplane-mode-toggle .ab-item::before,#wpadminbar>#wp-toolbar>#wp-admin-bar-root-default>#wp-admin-bar-airplane-mode-toggle .ab-icon{margin-right:0}.airplane-mode-no-qm #wpadminbar #wp-admin-bar-airplane-mode-toggle .ab-icon,.airplane-mode-no-qm #wpadminbar #wp-admin-bar-airplane-mode-toggle .ab-item::before,.airplane-mode-no-qm #wpadminbar>#wp-toolbar>#wp-admin-bar-root-default>#wp-admin-bar-airplane-mode-toggle .ab-icon{margin-right:6px}.airplane-mode-disabled #wp-admin-bar-airplane-mode-toggle .ab-label{-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";filter:alpha(opacity=50);opacity:.5}.airplane-mode-enabled .theme-browser.content-filterable.rendered,.airplane-mode-enabled.plugin-install-php .wp-filter,.airplane-mode-enabled.plugin-install-php h1 a.upload,.airplane-mode-enabled.plugin-install-php.branch-4-6 h1 a.upload-view-toggle,.airplane-mode-enabled.theme-install-php .wp-filter,.airplane-mode-enabled.theme-install-php a.browse-themes{display:none!important}
|
BIN
content/plugins/airplane-mode/lib/fonts/airplanemodeicons.eot
Normal file
BIN
content/plugins/airplane-mode/lib/fonts/airplanemodeicons.eot
Normal file
Binary file not shown.
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<metadata>Generated by Fontastic.me</metadata>
|
||||||
|
<defs>
|
||||||
|
<font id="airplanemodeicons" horiz-adv-x="512">
|
||||||
|
<font-face font-family="airplanemodeicons" units-per-em="512" ascent="480" descent="-32"/>
|
||||||
|
<missing-glyph horiz-adv-x="512" />
|
||||||
|
|
||||||
|
<glyph glyph-name="airplane-on" unicode="" d="M154 13l51 0 128 205 141 0c21 0 38 17 38 38 0 21-17 38-38 38l-141 0-128 205-51 0 64-205-141 0-39 52-38 0 26-90-26-90 38 0 39 52 141 0z"/>
|
||||||
|
<glyph glyph-name="airplane-off" unicode="" d="M333 294l141 0c21 0 38-17 38-38 0-21-17-38-38-38l-95 0-200 200-25 81 51 0z m95-256l-127 128-96-153-51 0 64 205-141 0-39-52-38 0 26 90-26 90 38 0 39-52 95 0-146 147 32 33 403-403z"/>
|
||||||
|
</font></defs></svg>
|
After Width: | Height: | Size: 858 B |
BIN
content/plugins/airplane-mode/lib/fonts/airplanemodeicons.ttf
Normal file
BIN
content/plugins/airplane-mode/lib/fonts/airplanemodeicons.ttf
Normal file
Binary file not shown.
BIN
content/plugins/airplane-mode/lib/fonts/airplanemodeicons.woff
Normal file
BIN
content/plugins/airplane-mode/lib/fonts/airplanemodeicons.woff
Normal file
Binary file not shown.
151
content/plugins/airplane-mode/readme.txt
Normal file
151
content/plugins/airplane-mode/readme.txt
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
=== Airplane Mode ===
|
||||||
|
Contributors: norcross, johnbillion, afragen, szepeviktor, chriscct7, markjaquith
|
||||||
|
Website Link: https://github.com/norcross/airplane-mode
|
||||||
|
Donate link: https://andrewnorcross.com/donate
|
||||||
|
Tags: external calls, HTTP
|
||||||
|
Requires at least: 4.4
|
||||||
|
Tested up to: 5.3
|
||||||
|
Stable tag: 0.2.5
|
||||||
|
License: MIT
|
||||||
|
License URI: http://norcross.mit-license.org/
|
||||||
|
|
||||||
|
Control loading of external files when developing locally
|
||||||
|
|
||||||
|
== Description ==
|
||||||
|
|
||||||
|
Control loading of external files when developing locally. WP loads certain external files (fonts, gravatar, etc) and makes external HTTP calls. This isn't usually an issue, unless you're working in an evironment without a web connection. This plugin removes / unhooks those actions to reduce load time and avoid errors due to missing files.
|
||||||
|
|
||||||
|
Features
|
||||||
|
|
||||||
|
* removes external JS and CSS files from loading
|
||||||
|
* replaces all instances of Gravatar with a local image to remove external call
|
||||||
|
* removes all HTTP requests
|
||||||
|
* disables all WP update checks for core, themes, and plugins
|
||||||
|
* includes toggle in admin bar for quick enable / disable
|
||||||
|
|
||||||
|
== Installation ==
|
||||||
|
|
||||||
|
This section describes how to install the plugin and get it working.
|
||||||
|
|
||||||
|
1. Upload `airplane-mode` to the `/wp-content/plugins/` directory.
|
||||||
|
1. Activate the plugin through the 'Plugins' menu in WordPress.
|
||||||
|
1. Toggle users as needed
|
||||||
|
|
||||||
|
If you need offline activation, see [this script](https://gist.github.com/solepixel/e1d03f4dcd1b9e86552b3cc6937325bf) written by [Brian DiChiara](https://github.com/solepixel)
|
||||||
|
|
||||||
|
== Frequently Asked Questions ==
|
||||||
|
|
||||||
|
= Why do I need this? =
|
||||||
|
|
||||||
|
Because you are a jet set developer who needs to work without internet.
|
||||||
|
|
||||||
|
|
||||||
|
== Screenshots ==
|
||||||
|
|
||||||
|
|
||||||
|
== Changelog ==
|
||||||
|
|
||||||
|
= 0.2.5 - 2019/11/15
|
||||||
|
* Remove usage of method that's been deprecated in WordPress trunk. props @johnbillion
|
||||||
|
|
||||||
|
= 0.2.4 - 2017/12/13
|
||||||
|
* Added localhost bypass for loading CSS and JS files when hot reloading. props @shadyvb
|
||||||
|
* Added additional hook removals for new automated updates and language packs.
|
||||||
|
* Removed outdated `create_function` call for PHP 7.2 compatibility. props @geminorum
|
||||||
|
* General cleanup
|
||||||
|
|
||||||
|
= 0.2.3 - 2017/03/05
|
||||||
|
* Adding option to force transient purge
|
||||||
|
* Adding `clean` action to WP-CLI functions
|
||||||
|
|
||||||
|
= 0.2.2 - 2016/12/07
|
||||||
|
* Adding WP-CLI support. props @markjaquith
|
||||||
|
|
||||||
|
= 0.2.1 - 2016/10/19
|
||||||
|
* Fix settings page language drowdown when used offline. props @onnimonni
|
||||||
|
|
||||||
|
= 0.2.0 - 2016/08/26
|
||||||
|
* modify CSS loading for front-end, removed loading for login page. props @barryceelen
|
||||||
|
* fixed WP.org theme API call request to avoid `WP_Error` return. props @onnimonni
|
||||||
|
|
||||||
|
= 0.1.9 - 2016/07/25
|
||||||
|
* Prevent BuddyPress from falling back to Gravatar. props @johnbillion
|
||||||
|
|
||||||
|
= 0.1.8 - 2016/07/12
|
||||||
|
* allow `JETPACK_DEV_DEBUG` constant to take priority over filter. props @kopepasah
|
||||||
|
* added additional CSS for upcoming 4.6. change to upload tab.
|
||||||
|
|
||||||
|
= 0.1.7 - 2016/05/18
|
||||||
|
* allow local HTTP calls with optional filter. props @johnbillion
|
||||||
|
* add back index.php link to main dashboard menu item
|
||||||
|
* bumped minimum WP version requirement to 4.4
|
||||||
|
|
||||||
|
= 0.1.6 - 2016/04/25
|
||||||
|
* minor tweak to include CSS for new icon font
|
||||||
|
|
||||||
|
= 0.1.5 - 2016/04/24
|
||||||
|
* adding custom icon font for display and removing label. props @barryceelen
|
||||||
|
|
||||||
|
= 0.1.4 - 2016/02/26
|
||||||
|
* better setup for blocked external assets. props @johnbillion
|
||||||
|
|
||||||
|
= 0.1.3 - 2016/02/22
|
||||||
|
* modified CSS rules to fix media bulk actions bar from disappearing
|
||||||
|
* moved `airplane_mode_status_change` action to run before redirect, and now includes the status being run.
|
||||||
|
|
||||||
|
= 0.1.2 - 2016/01/09
|
||||||
|
* added back HTTP count when inactive
|
||||||
|
* removed HTTP count completely when Query Monitor is active
|
||||||
|
|
||||||
|
= 0.1.1 - 2016/01/06
|
||||||
|
* fixed incorrect nonce check that was breaking toggle
|
||||||
|
* changed CSS and JS checks to include all themes and plugins as well as core
|
||||||
|
|
||||||
|
= 0.1.0 - 2015/12/30
|
||||||
|
* added `airplane_mode_purge_transients` filter to bypass transient purge
|
||||||
|
|
||||||
|
= 0.0.9 - 2015/12/07
|
||||||
|
* changed from colored circle to actual airplane icon for usability
|
||||||
|
* fixed dashboard link icon for multisite
|
||||||
|
* changed to exclude all external stylesheets, not just Open Sans
|
||||||
|
* added language files for translateable goodness
|
||||||
|
* general cleanup for WP coding standards
|
||||||
|
|
||||||
|
= 0.0.8 - 2015/05/18
|
||||||
|
* added `class_exists` as now included in DesktopServer and collisions could result
|
||||||
|
* fixed `if ( ! defined ...` for `AIRMDE_BASE` constant
|
||||||
|
|
||||||
|
= 0.0.7 - 2015/04/21 =
|
||||||
|
* fixed some CSS from hiding plugins page bar
|
||||||
|
* moved changelog to it's own file
|
||||||
|
* added `composer.json`
|
||||||
|
* added contributors to readme
|
||||||
|
* clarified license (MIT)
|
||||||
|
|
||||||
|
= 0.0.6 - 2015/04/02 =
|
||||||
|
* version bump for GitHub updater
|
||||||
|
|
||||||
|
= 0.0.5 - 2015/04/02 =
|
||||||
|
* fixed bug in update logic that was preventing checks when disabled (but activated). props @johnbillion
|
||||||
|
|
||||||
|
= 0.0.4 - 2015/04/02 =
|
||||||
|
* added multiple checks for all the various theme and plugin update calls. props @chriscct7
|
||||||
|
* added HTTP counter on a per-page basis. props @szepeviktor
|
||||||
|
|
||||||
|
= 0.0.3 - 2015/01/23 =
|
||||||
|
* added `airplane_mode_status_change` hook for functions to fire on status change
|
||||||
|
* purge update related transients on disable
|
||||||
|
* added WordPress formatted readme file
|
||||||
|
|
||||||
|
= 0.0.2 - 2015/01/21 =
|
||||||
|
* added GitHub Updater support
|
||||||
|
* fixed update capabilities when status is disabled
|
||||||
|
|
||||||
|
= 0.0.1 - 2014/06/01 =
|
||||||
|
* lots of stuff. I wasn't keeping a changelog. I apologize.
|
||||||
|
|
||||||
|
|
||||||
|
== Upgrade Notice ==
|
||||||
|
|
||||||
|
= 0.0.1 =
|
||||||
|
Initial release
|
121
content/plugins/f-api/inc/events/namespace.php
Normal file
121
content/plugins/f-api/inc/events/namespace.php
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Community events API.
|
||||||
|
*
|
||||||
|
* Powers the dashboard events widget.
|
||||||
|
*
|
||||||
|
* We store the cache for two hours, but update hourly, to ensure we always hit
|
||||||
|
* the cache.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FAIRServer\API\Events;
|
||||||
|
|
||||||
|
use FAIRServer\API;
|
||||||
|
use WP_Error;
|
||||||
|
use WP_HTTP;
|
||||||
|
use WP_REST_Request;
|
||||||
|
use WP_REST_Response;
|
||||||
|
use WP_REST_Server;
|
||||||
|
|
||||||
|
const CACHE_GROUP = 'fairserver';
|
||||||
|
const CACHE_KEY = 'events';
|
||||||
|
const CACHE_LIFETIME = 2 * HOUR_IN_SECONDS;
|
||||||
|
const CRON_HOOK = 'fairserver_events_cron';
|
||||||
|
const CRON_SCHEDULE = 'hourly';
|
||||||
|
const HTTP_CACHE_MAXAGE = 10 * MINUTE_IN_SECONDS;
|
||||||
|
const HTTP_CACHE_SWR = 50 * MINUTE_IN_SECONDS;
|
||||||
|
const SOURCE_URL = 'https://thewp.world/wp-json/wp/v2/wordcamp?per_page=100';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap events API.
|
||||||
|
*/
|
||||||
|
function bootstrap() : void {
|
||||||
|
add_action( 'init', __NAMESPACE__ . '\\schedule_cron' );
|
||||||
|
add_action( 'rest_api_init', __NAMESPACE__ . '\\register_routes' );
|
||||||
|
add_action( CRON_HOOK, __NAMESPACE__ . '\\refresh_events' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull data on an hourly basis.
|
||||||
|
*/
|
||||||
|
function schedule_cron() : void {
|
||||||
|
if ( ! wp_next_scheduled( CRON_HOOK ) ) {
|
||||||
|
wp_schedule_event( time(), CRON_SCHEDULE, CRON_HOOK );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the REST API routes.
|
||||||
|
*/
|
||||||
|
function register_routes() : void {
|
||||||
|
register_rest_route( API\API_NAMESPACE, '/events', [
|
||||||
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
'callback' => __NAMESPACE__ . '\get_events',
|
||||||
|
'permission_callback' => '__return_true',
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh events data from thewp.world.
|
||||||
|
*
|
||||||
|
* This is refreshed hourly, so we always refresh before the cache expires.
|
||||||
|
*/
|
||||||
|
function refresh_events() : void {
|
||||||
|
$response = wp_remote_get( SOURCE_URL, [
|
||||||
|
'user-agent' => 'FAIR Server/1.0',
|
||||||
|
] );
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
trigger_error(
|
||||||
|
'Failed to fetch events from thewp.world: ' . $response->get_error_message(),
|
||||||
|
E_USER_WARNING
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = wp_remote_retrieve_body( $response );
|
||||||
|
if ( empty( $body ) ) {
|
||||||
|
trigger_error(
|
||||||
|
'Failed to fetch events from thewp.world: Empty response body',
|
||||||
|
E_USER_WARNING
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$events = json_decode( $body, true );
|
||||||
|
if ( json_last_error() !== JSON_ERROR_NONE ) {
|
||||||
|
trigger_error(
|
||||||
|
'Failed to decode events from thewp.world: ' . json_last_error_msg(),
|
||||||
|
E_USER_WARNING
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_cache_set( CACHE_KEY, $events, CACHE_GROUP, CACHE_LIFETIME );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the latest community events.
|
||||||
|
*
|
||||||
|
* @param WP_REST_Request $request The request object.
|
||||||
|
* @return WP_REST_Response|WP_Error Response object on success or error.
|
||||||
|
*/
|
||||||
|
function get_events( WP_REST_Request $request ) {
|
||||||
|
$data = wp_cache_get( CACHE_KEY, CACHE_GROUP );
|
||||||
|
if ( ! $data ) {
|
||||||
|
return new WP_Error(
|
||||||
|
'fairserver.events.not_found',
|
||||||
|
'Unable to fetch events.',
|
||||||
|
[ 'status' => WP_Http::INTERNAL_SERVER_ERROR ]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = rest_ensure_response( $data );
|
||||||
|
$response->header(
|
||||||
|
'Cache-Control',
|
||||||
|
sprintf(
|
||||||
|
'max-age=%d, stale-while-revalidate=%d',
|
||||||
|
HTTP_CACHE_MAXAGE,
|
||||||
|
HTTP_CACHE_SWR
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return $response;
|
||||||
|
}
|
46
content/plugins/f-api/inc/namespace.php
Normal file
46
content/plugins/f-api/inc/namespace.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FAIRServer\API;
|
||||||
|
|
||||||
|
const API_NAMESPACE = 'fair/v1';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boostrap the API system.
|
||||||
|
*/
|
||||||
|
function bootstrap() : void {
|
||||||
|
add_filter( 'rest_url_prefix', '__return_empty_string' );
|
||||||
|
|
||||||
|
// Remove sitemaps.
|
||||||
|
remove_action( 'init', 'wp_sitemaps_get_server' );
|
||||||
|
|
||||||
|
// Remove default rewrites.
|
||||||
|
add_filter( 'post_rewrite_rules', '__return_empty_array' );
|
||||||
|
add_filter( 'date_rewrite_rules', '__return_empty_array' );
|
||||||
|
add_filter( 'comments_rewrite_rules', '__return_empty_array' );
|
||||||
|
add_filter( 'search_rewrite_rules', '__return_empty_array' );
|
||||||
|
add_filter( 'author_rewrite_rules', '__return_empty_array' );
|
||||||
|
add_filter( 'category_rewrite_rules', '__return_empty_array' );
|
||||||
|
add_filter( 'tag_rewrite_rules', '__return_empty_array' );
|
||||||
|
add_filter( 'post_format_rewrite_rules', '__return_empty_array' );
|
||||||
|
add_filter( 'page_rewrite_rules', '__return_empty_array' );
|
||||||
|
add_filter( 'root_rewrite_rules', '__return_empty_array' );
|
||||||
|
|
||||||
|
// Register our own rewrites.
|
||||||
|
add_action( 'init', __NAMESPACE__ . '\\register_rewrites' );
|
||||||
|
|
||||||
|
Events\bootstrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register rewrites to handle all API requests.
|
||||||
|
*
|
||||||
|
* By default, rest_get_url_prefix() being set to an empty string will not
|
||||||
|
* correctly trigger rewrites due to the leading slash. We manually register
|
||||||
|
* rewrites to handle this.
|
||||||
|
*/
|
||||||
|
function register_rewrites() : void {
|
||||||
|
// Our regular rule would match, but WP::parse_request manually checks for
|
||||||
|
// a specific '$' rule.
|
||||||
|
add_rewrite_rule( '$', 'index.php?rest_route=/', 'bottom' );
|
||||||
|
add_rewrite_rule( '^(.*)?', 'index.php?rest_route=/$matches[1]', 'bottom' );
|
||||||
|
}
|
12
content/plugins/f-api/plugin.php
Normal file
12
content/plugins/f-api/plugin.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Plugin Name: FAIR Server - API
|
||||||
|
* Description: API component of the FAIR Server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FAIRServer\API;
|
||||||
|
|
||||||
|
require __DIR__ . '/inc/namespace.php';
|
||||||
|
require __DIR__ . '/inc/events/namespace.php';
|
||||||
|
|
||||||
|
bootstrap();
|
125
content/plugins/f-gd-cron/inc/namespace.php
Normal file
125
content/plugins/f-gd-cron/inc/namespace.php
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace FAIRServer\GD_Cron;
|
||||||
|
|
||||||
|
const CRON_HOOK = 'fairserver_gd_cron';
|
||||||
|
const CRON_SCHEDULE = 'f-5min';
|
||||||
|
const MAX_SITES = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap.
|
||||||
|
*/
|
||||||
|
function bootstrap() : void {
|
||||||
|
add_action( 'plugins_loaded', __NAMESPACE__ . '\\load' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the actions, for the main site only.
|
||||||
|
*/
|
||||||
|
function load() : void {
|
||||||
|
if ( ! is_multisite() || ! is_main_site() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_filter( 'cron_schedules', __NAMESPACE__ . '\\add_cron_schedule' );
|
||||||
|
add_action( 'init', function () {
|
||||||
|
if ( ! wp_next_scheduled( CRON_HOOK ) ) {
|
||||||
|
wp_schedule_event( time(), CRON_SCHEDULE, CRON_HOOK );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
add_action( CRON_HOOK, __NAMESPACE__ . '\\run_cron' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register our 5 minute schedule.
|
||||||
|
*
|
||||||
|
* @param array $schedules Existing schedules.
|
||||||
|
* @return array Modified schedules.
|
||||||
|
*/
|
||||||
|
function add_cron_schedule( array $schedules ) : array {
|
||||||
|
$schedules[ CRON_SCHEDULE ] = [
|
||||||
|
'interval' => 5 * MINUTE_IN_SECONDS,
|
||||||
|
'display' => __( 'FAIR Server - GD Cron', 'fairserver' ),
|
||||||
|
];
|
||||||
|
|
||||||
|
return $schedules;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the cron job on all sites.
|
||||||
|
*
|
||||||
|
* GD only runs wp-cron on the main site, so we need to force-spawn it on all
|
||||||
|
* sites.
|
||||||
|
*/
|
||||||
|
function run_cron() : void {
|
||||||
|
$sites = get_sites( [
|
||||||
|
'number' => MAX_SITES,
|
||||||
|
'fields' => 'ids',
|
||||||
|
] );
|
||||||
|
|
||||||
|
foreach ( $sites as $site_id ) {
|
||||||
|
switch_to_blog( $site_id );
|
||||||
|
run_site_cron();
|
||||||
|
restore_current_blog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn cron for the current site.
|
||||||
|
*
|
||||||
|
* @internal Copy of spawn_cron() to allow recursive spawning.
|
||||||
|
*/
|
||||||
|
function run_site_cron() : bool {
|
||||||
|
$gmt_time = microtime( true );
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the cron lock, which is a Unix timestamp of when the last cron was spawned
|
||||||
|
* and has not finished running.
|
||||||
|
*
|
||||||
|
* Multiple processes on multiple web servers can run this code concurrently,
|
||||||
|
* this lock attempts to make spawning as atomic as possible.
|
||||||
|
*/
|
||||||
|
$lock = (float) get_transient( 'doing_cron' );
|
||||||
|
|
||||||
|
if ( $lock > $gmt_time + 10 * MINUTE_IN_SECONDS ) {
|
||||||
|
$lock = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't run if another process is currently running it or more than once every 60 sec.
|
||||||
|
if ( $lock + WP_CRON_LOCK_TIMEOUT > $gmt_time ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confidence check.
|
||||||
|
$crons = wp_get_ready_cron_jobs();
|
||||||
|
if ( empty( $crons ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$keys = array_keys( $crons );
|
||||||
|
if ( isset( $keys[0] ) && $keys[0] > $gmt_time ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the cron lock with the current unix timestamp, when the cron is being spawned.
|
||||||
|
$doing_wp_cron = sprintf( '%.22F', $gmt_time );
|
||||||
|
set_transient( 'doing_cron', $doing_wp_cron );
|
||||||
|
|
||||||
|
$cron_request = apply_filters(
|
||||||
|
'cron_request',
|
||||||
|
array(
|
||||||
|
'url' => add_query_arg( 'doing_wp_cron', $doing_wp_cron, site_url( 'wp-cron.php' ) ),
|
||||||
|
'key' => $doing_wp_cron,
|
||||||
|
'args' => array(
|
||||||
|
'timeout' => 0.01,
|
||||||
|
'blocking' => false,
|
||||||
|
/** This filter is documented in wp-includes/class-wp-http-streams.php */
|
||||||
|
'sslverify' => apply_filters( 'https_local_ssl_verify', false ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
$doing_wp_cron
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = wp_remote_post( $cron_request['url'], $cron_request['args'] );
|
||||||
|
return ! is_wp_error( $result );
|
||||||
|
}
|
12
content/plugins/f-gd-cron/plugin.php
Normal file
12
content/plugins/f-gd-cron/plugin.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Plugin Name: FAIR Server - GD Cron
|
||||||
|
* Description: Fix GD cron jobs for multisite.
|
||||||
|
* Network: true
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FAIRServer\GD_Cron;
|
||||||
|
|
||||||
|
require __DIR__ . '/inc/namespace.php';
|
||||||
|
|
||||||
|
bootstrap();
|
109
content/plugins/f-translate/inc/class-projectcommand.php
Normal file
109
content/plugins/f-translate/inc/class-projectcommand.php
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<?php
|
||||||
|
namespace FAIRServer\Translate;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use GP;
|
||||||
|
use GP_Locales;
|
||||||
|
use GP_Project;
|
||||||
|
use WP_CLI;
|
||||||
|
use WP_CLI_Command;
|
||||||
|
|
||||||
|
class ProjectCommand extends WP_CLI_Command {
|
||||||
|
/**
|
||||||
|
* Import projects from a projects.json file.
|
||||||
|
*
|
||||||
|
* <file>
|
||||||
|
* : The path to the projects.json file (use glotpress-dump to generate)
|
||||||
|
*
|
||||||
|
* @subcommand import
|
||||||
|
*/
|
||||||
|
public function import( $args ) {
|
||||||
|
if ( $args[0] !== '-' ) {
|
||||||
|
$path = realpath( $args[0] );
|
||||||
|
$data = file_get_contents( $path );
|
||||||
|
} else {
|
||||||
|
$data = file_get_contents( 'php://stdin' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = json_decode( $data );
|
||||||
|
|
||||||
|
foreach ( $items as $item ) {
|
||||||
|
// Does this project already exist?
|
||||||
|
$existing = GP::$project->get( $item->id );
|
||||||
|
if ( $existing ) {
|
||||||
|
// Update the existing project.
|
||||||
|
$project = $this->sync_project( $item );
|
||||||
|
} else {
|
||||||
|
// Manually create, to ensure the ID matches.
|
||||||
|
$project = $this->import_project( $item );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// var_dump( $projects );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function import_project( object $data ) : GP_Project {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$args = [
|
||||||
|
'name' => $data->name,
|
||||||
|
'slug' => $data->slug,
|
||||||
|
'description' => $data->description,
|
||||||
|
'parent_project_id' => $data->parent_project_id ?? 0,
|
||||||
|
'source_url_template' => $data->source_url_template,
|
||||||
|
'active' => $data->active,
|
||||||
|
];
|
||||||
|
$args = GP::$project->prepare_fields_for_save( $args );
|
||||||
|
$args = GP::$project->prepare_fields_for_create( $args );
|
||||||
|
|
||||||
|
// Manually set the ID, to ensure it matches.
|
||||||
|
// This would otherwise be stripped in prepare_fields_for_save
|
||||||
|
$args['id'] = $data->id;
|
||||||
|
|
||||||
|
$field_formats = GP::$project->get_db_field_formats( $args );
|
||||||
|
|
||||||
|
// Let it rip.
|
||||||
|
$res = $wpdb->insert( GP::$project->table, $args, $field_formats );
|
||||||
|
if ( false === $res ) {
|
||||||
|
throw new Exception(
|
||||||
|
sprintf(
|
||||||
|
'Failed to create project: %s',
|
||||||
|
$wpdb->last_error
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$inserted = new GP_Project( $args );
|
||||||
|
$inserted->id = $wpdb->insert_id;
|
||||||
|
$inserted->after_create();
|
||||||
|
|
||||||
|
return $inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sync_project( object $data ) : GP_Project {
|
||||||
|
$project = GP::$project->get( $data->id );
|
||||||
|
if ( ! $project ) {
|
||||||
|
throw new Exception( 'Project not found' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$needs_update = false;
|
||||||
|
$fields = GP::$project->field_names;
|
||||||
|
foreach ( $fields as $field ) {
|
||||||
|
if ( in_array( $field, GP::$project->non_updatable_attributes, true ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $project->$field !== $data->$field ) {
|
||||||
|
$needs_update = true;
|
||||||
|
$project->$field = $data->$field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Needs update.
|
||||||
|
if ( $needs_update ) {
|
||||||
|
WP_CLI::line( sprintf( 'Updating project %d (%s)', $project->id, $project->slug ) );
|
||||||
|
// $project->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $project;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
namespace FAIRServer\Translate;
|
||||||
|
|
||||||
|
use GP;
|
||||||
|
use GP_Locales;
|
||||||
|
use WP_CLI;
|
||||||
|
use WP_CLI_Command;
|
||||||
|
|
||||||
|
class TranslationSetCommand extends WP_CLI_Command {
|
||||||
|
/**
|
||||||
|
* Ensure a translation set exists.
|
||||||
|
*
|
||||||
|
* <project>
|
||||||
|
* : Project slug.
|
||||||
|
*
|
||||||
|
* <locale>
|
||||||
|
* : Locale slug.
|
||||||
|
*
|
||||||
|
* [--set=<set>]
|
||||||
|
* : Translation set slug; default is "default"
|
||||||
|
*
|
||||||
|
* [--verbose]
|
||||||
|
* : Output additional information during execution.
|
||||||
|
*
|
||||||
|
* @subcommand ensure
|
||||||
|
*/
|
||||||
|
public function ensure_translation_set( $args, $assoc_args ) {
|
||||||
|
$project = $args[0];
|
||||||
|
$locale = $args[1];
|
||||||
|
$set = $assoc_args['set'] ?? 'default';
|
||||||
|
|
||||||
|
$project = GP::$project->by_path( $project );
|
||||||
|
if ( ! $project ) {
|
||||||
|
WP_CLI::error( 'Project not found!', 'glotpress' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$gp_locale = GP_Locales::by_slug( $locale );
|
||||||
|
if ( ! $gp_locale ) {
|
||||||
|
WP_CLI::error( 'Locale not found!', 'glotpress' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translation_set = GP::$translation_set->by_project_id_slug_and_locale( $project->id, $set, $gp_locale->slug );
|
||||||
|
if ( $translation_set ) {
|
||||||
|
// Silently exit, unless verbose.
|
||||||
|
if ( $assoc_args['verbose'] ?? false ) {
|
||||||
|
WP_CLI::success( 'Already exists.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the translation set.
|
||||||
|
GP::$translation_set->create( [
|
||||||
|
'name' => $gp_locale->english_name,
|
||||||
|
'slug' => $set,
|
||||||
|
'project_id' => $project->id,
|
||||||
|
'locale' => $gp_locale->slug,
|
||||||
|
] );
|
||||||
|
WP_CLI::success( 'Translation set created.' );
|
||||||
|
}
|
||||||
|
}
|
24
content/plugins/f-translate/inc/namespace.php
Normal file
24
content/plugins/f-translate/inc/namespace.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
namespace FAIRServer\Translate;
|
||||||
|
|
||||||
|
use WP_CLI;
|
||||||
|
|
||||||
|
function bootstrap() {
|
||||||
|
|
||||||
|
add_action( 'gp_tmpl_load_locations', __NAMESPACE__ . '\\add_template_locations', 50, 4 );
|
||||||
|
|
||||||
|
// Load the command class.
|
||||||
|
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||||
|
// WP_CLI::add_command( 'fair-translate', '__return_null' );
|
||||||
|
WP_CLI::add_command( 'fair-translate project', __NAMESPACE__ . '\\ProjectCommand' );
|
||||||
|
WP_CLI::add_command( 'fair-translate translation-set', __NAMESPACE__ . '\\TranslationSetCommand' );
|
||||||
|
}
|
||||||
|
Stats\bootstrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_template_locations( $locations, $template, $args, $template_path ) {
|
||||||
|
// Add the theme directories to the template locations.
|
||||||
|
$locations[] = get_stylesheet_directory() . '/glotpress';
|
||||||
|
$locations[] = get_template_directory() . '/glotpress';
|
||||||
|
return $locations;
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Discarded warning stats.
|
||||||
|
*
|
||||||
|
* Originally from https://github.com/WordPress/wordpress.org/blob/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-custom-stats/stats/discarded-warning.php
|
||||||
|
* Used under the GPLv2 license. Copyright 2025 authors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FAIRServer\Translate\Stats;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This plugin logs discarded warnings into a database table.
|
||||||
|
*
|
||||||
|
* @author Dominik Schilling (ocean90)
|
||||||
|
*/
|
||||||
|
class DiscardedWarningStats {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the table name for logging warnings.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $table_name = 'translate_dotorg_warnings';
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
add_action( 'gp_warning_discarded', array( $this, 'log_discarded_warning' ), 10, 5 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs discared warnings into a table.
|
||||||
|
*
|
||||||
|
* @param int $project_id ID of the project.
|
||||||
|
* @param int $translation_set ID if the translation set.
|
||||||
|
* @param int $translation ID of the translation.
|
||||||
|
* @param string $warning Key of the warning. (length, tags, placeholders, both_begin_end_on_newlines)
|
||||||
|
* @param int $user ID of the user.
|
||||||
|
*/
|
||||||
|
public function log_discarded_warning( $project_id, $translation_set, $translation, $warning, $user ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$warning = array(
|
||||||
|
'project_id' => $project_id,
|
||||||
|
'translation_set' => $translation_set,
|
||||||
|
'translation' => $translation,
|
||||||
|
'warning' => $warning,
|
||||||
|
'user' => $user,
|
||||||
|
'timestamp' => gmdate( 'Y-m-d H:i:s' ),
|
||||||
|
'status' => 'needs-review'
|
||||||
|
);
|
||||||
|
$format = array( '%d', '%d', '%d', '%s', '%d', '%s', '%s' );
|
||||||
|
$wpdb->insert(
|
||||||
|
$this->table_name,
|
||||||
|
$warning,
|
||||||
|
$format
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get_schema() : string {
|
||||||
|
global $gp_table_prefix;
|
||||||
|
return <<<"END"
|
||||||
|
CREATE TABLE `{$gp_table_prefix}dotorg_warnings` (
|
||||||
|
`ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`project_id` bigint(20) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`translation_set` bigint(20) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`translation` bigint(20) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`warning` varchar(20) NOT NULL DEFAULT '',
|
||||||
|
`user` bigint(20) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`timestamp` datetime NOT NULL default '0000-00-00 00:00:00',
|
||||||
|
`status` varchar(20) NOT NULL DEFAULT '',
|
||||||
|
PRIMARY KEY (`ID`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||||
|
END;
|
||||||
|
}
|
||||||
|
}
|
235
content/plugins/f-translate/inc/stats/class-projectstats.php
Normal file
235
content/plugins/f-translate/inc/stats/class-projectstats.php
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Discarded warning stats.
|
||||||
|
*
|
||||||
|
* Originally from https://github.com/WordPress/wordpress.org/blob/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-custom-stats/stats/project.php
|
||||||
|
* Used under the GPLv2 license. Copyright 2025 authors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FAIRServer\Translate\Stats;
|
||||||
|
|
||||||
|
use GP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This plugin stores the Translation Count Status into a DB table for querying purposes.
|
||||||
|
*
|
||||||
|
* The data is pulled from GP_Translation_Set stat functions and updated in the DB whenever
|
||||||
|
* a new translation is submitted, or new originals are imported.
|
||||||
|
* The datbase update is delayed until shutdown to bulk-update the database during imports.
|
||||||
|
*
|
||||||
|
* NOTE: The counts includes all sub-projects in the count, as that's more useful for querying (top-level projects excluded)
|
||||||
|
* for example, wp-plugins/akismet will include wp-plugins/akismet/stable and wp-plugins/akismet/stable-readme
|
||||||
|
* wp-plugins and wp-themes are special top-level projects which are included here, where their string counts are updated
|
||||||
|
* on a twice-daily cron task.
|
||||||
|
*
|
||||||
|
* @author dd32
|
||||||
|
*/
|
||||||
|
class ProjectStats {
|
||||||
|
|
||||||
|
public $projects_to_update = array();
|
||||||
|
|
||||||
|
function __construct() {
|
||||||
|
global $wpdb, $gp_table_prefix;
|
||||||
|
|
||||||
|
add_action( 'gp_translation_created', array( $this, 'translation_edited' ) );
|
||||||
|
add_action( 'gp_translation_saved', array( $this, 'translation_edited' ) );
|
||||||
|
add_action( 'gp_translation_deleted', array( $this, 'translation_edited' ) );
|
||||||
|
add_action( 'gp_originals_imported', array( $this, 'originals_imported' ), 10, 5 );
|
||||||
|
|
||||||
|
// DB Writes are delayed until shutdown to bulk-update the stats during imports
|
||||||
|
add_action( 'shutdown', array( $this, 'shutdown' ) );
|
||||||
|
|
||||||
|
// Cron task to cache the wp-themes/wp-plugins string counts.
|
||||||
|
if ( ! wp_next_scheduled ( 'wporg_gp_stats_cache_waiting_strings' ) ) {
|
||||||
|
wp_schedule_event( time(), 'twicedaily', 'wporg_gp_stats_cache_waiting_strings' );
|
||||||
|
}
|
||||||
|
add_action( 'wporg_gp_stats_cache_waiting_strings', [ $this, 'cache_wp_themes_wp_plugins_strings' ] );
|
||||||
|
|
||||||
|
$wpdb->project_translation_status = $gp_table_prefix . 'project_translation_status';
|
||||||
|
}
|
||||||
|
|
||||||
|
function translation_edited( $translation ) {
|
||||||
|
/** @var \GP_Translation_Set */
|
||||||
|
$set = GP::$translation_set->get( $translation->translation_set_id );
|
||||||
|
$this->projects_to_update[ $set->project_id ][ $set->locale . '/' . $set->slug ] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function originals_imported( $project_id, $originals_added, $originals_existing, $originals_obsoleted, $originals_fuzzied ) {
|
||||||
|
if ( $originals_added || $originals_existing || $originals_obsoleted || $originals_fuzzied ) {
|
||||||
|
$this->projects_to_update[ $project_id ] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count up all the strings
|
||||||
|
function get_project_translation_counts( $project_id, $locale, $locale_slug, &$counts = array() ) {
|
||||||
|
if ( ! $counts ) {
|
||||||
|
$counts = array( 'all' => 0, 'current' => 0, 'waiting' => 0, 'fuzzy' => 0, 'warnings' => 0, 'untranslated' => 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not all projects have translation sets
|
||||||
|
/** @var \GP_Translation_Set */
|
||||||
|
$set = GP::$translation_set->by_project_id_slug_and_locale( $project_id, $locale_slug, $locale );
|
||||||
|
if ( $set ) {
|
||||||
|
// Force a refresh of the translation set counts
|
||||||
|
wp_cache_delete( $set->id, 'translation_set_status_breakdown' );
|
||||||
|
|
||||||
|
$counts['all'] += $set->all_count();
|
||||||
|
$counts['current'] += $set->current_count();
|
||||||
|
$counts['waiting'] += $set->waiting_count();
|
||||||
|
$counts['fuzzy'] += $set->fuzzy_count();
|
||||||
|
$counts['warnings'] += $set->warnings_count();
|
||||||
|
$counts['untranslated'] += $set->untranslated_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the strings from the sub projects too
|
||||||
|
/** @var \GP_Project */
|
||||||
|
foreach ( GP::$project->get( $project_id )->sub_projects() as $project ) {
|
||||||
|
if ( ! $project->active ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->get_project_translation_counts( $project->id, $locale, $locale_slug, $counts );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cron task to cache the string counts for the wp-themes and wp-plugins parent categories.
|
||||||
|
*
|
||||||
|
* These don't have any translation sets, but we need to be able to query the waiting strings for them.
|
||||||
|
*/
|
||||||
|
public function cache_wp_themes_wp_plugins_strings() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$cached_projects = [
|
||||||
|
GP::$project->by_path( 'wp-plugins' ),
|
||||||
|
GP::$project->by_path( 'wp-themes' ),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Store the counts for these parent projects as the sum of their children.
|
||||||
|
$sql = "INSERT INTO {$wpdb->project_translation_status} ( `project_id`, `locale`, `locale_slug`, `all`, `current`, `waiting`, `fuzzy`, `warnings`, `untranslated`, `date_added`, `date_modified`)
|
||||||
|
SELECT
|
||||||
|
p.parent_project_id as project_id,
|
||||||
|
locale, locale_slug,
|
||||||
|
SUM( stats.all ) as `all`, SUM( stats.current ) as `current`, SUM( stats.waiting ) as `waiting`,
|
||||||
|
SUM( stats.fuzzy ) as `fuzzy`, SUM( stats.warnings ) as `warnings`, SUM( stats.untranslated ) as `untranslated`,
|
||||||
|
NOW() as `date_added`, NOW() as `date_modified`
|
||||||
|
FROM {$wpdb->project_translation_status} stats
|
||||||
|
LEFT JOIN {$wpdb->gp_projects} p ON stats.project_id = p.id
|
||||||
|
WHERE
|
||||||
|
p.parent_project_id = %d
|
||||||
|
AND p.active = 1
|
||||||
|
GROUP BY locale, locale_slug
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
`all` = VALUES(`all`), `current` = VALUES(`current`),
|
||||||
|
`waiting` = VALUES(`waiting`), `fuzzy` = VALUES(`fuzzy`),
|
||||||
|
`warnings` = VALUES(`warnings`), `untranslated` = VALUES(`untranslated`),
|
||||||
|
`date_modified` = VALUES(`date_modified`);
|
||||||
|
";
|
||||||
|
|
||||||
|
foreach ( $cached_projects as $project ) {
|
||||||
|
$wpdb->query( $wpdb->prepare( $sql, $project->id ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function shutdown() {
|
||||||
|
global $wpdb;
|
||||||
|
$values = array();
|
||||||
|
|
||||||
|
// If a project is `true` then we need to fetch all translation sets for it.
|
||||||
|
foreach ( $this->projects_to_update as $project_id => $set_data ) {
|
||||||
|
if ( true === $set_data ) {
|
||||||
|
$this->projects_to_update[ $project_id ] = array();
|
||||||
|
/** @var \GP_Translation_Set */
|
||||||
|
foreach ( GP::$translation_set->by_project_id( $project_id ) as $set ) {
|
||||||
|
$this->projects_to_update[ $project_id ][ $set->locale . '/' . $set->slug ] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all parent projects as well.
|
||||||
|
// This does NOT update a root parent (ie. ! $parent_project_id) as we use those as grouping categories.
|
||||||
|
$projects = $this->projects_to_update;
|
||||||
|
foreach ( $projects as $project_id => $data ) {
|
||||||
|
// Do all parents
|
||||||
|
/** @var \GP_Project */
|
||||||
|
$project = GP::$project->get( $project_id );
|
||||||
|
while ( $project ) {
|
||||||
|
/** @var \GP_Project */
|
||||||
|
$project = GP::$project->get( $project->parent_project_id );
|
||||||
|
if ( $project->parent_project_id ) {
|
||||||
|
$projects[ $project->id ] = $data;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->projects_to_update += $projects;
|
||||||
|
unset( $projects );
|
||||||
|
|
||||||
|
$now = current_time( 'mysql', 1 );
|
||||||
|
|
||||||
|
foreach ( $this->projects_to_update as $project_id => $locale_sets ) {
|
||||||
|
$locale_sets = array_keys( $locale_sets );
|
||||||
|
$locale_sets = array_map( function( $set ) { return explode( '/', $set ); }, $locale_sets );
|
||||||
|
|
||||||
|
foreach ( $locale_sets as $locale_set ) {
|
||||||
|
list( $locale, $locale_slug ) = $locale_set;
|
||||||
|
$counts = $this->get_project_translation_counts( $project_id, $locale, $locale_slug );
|
||||||
|
|
||||||
|
$values[] = $wpdb->prepare( '(%d, %s, %s, %d, %d, %d, %d, %d, %d, %s, %s)',
|
||||||
|
$project_id,
|
||||||
|
$locale,
|
||||||
|
$locale_slug,
|
||||||
|
$counts['all'],
|
||||||
|
$counts['current'],
|
||||||
|
$counts['waiting'],
|
||||||
|
$counts['fuzzy'],
|
||||||
|
$counts['warnings'],
|
||||||
|
$counts['untranslated'],
|
||||||
|
$now,
|
||||||
|
$now
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're processing a large batch, add them as we go to avoid query lengths & memory limits
|
||||||
|
if ( count( $values ) > 50 ) {
|
||||||
|
$wpdb->query( "INSERT INTO {$wpdb->project_translation_status} (`project_id`, `locale`, `locale_slug`, `all`, `current`, `waiting`, `fuzzy`, `warnings`, `untranslated`, `date_added`, `date_modified` ) VALUES " . implode( ', ', $values ) . " ON DUPLICATE KEY UPDATE `all`=VALUES(`all`), `current`=VALUES(`current`), `waiting`=VAlUES(`waiting`), `fuzzy`=VALUES(`fuzzy`), `warnings`=VALUES(`warnings`), `untranslated`=VALUES(`untranslated`), `date_modified`=VALUES(`date_modified`)" );
|
||||||
|
$values = array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->projects_to_update = array();
|
||||||
|
|
||||||
|
if ( $values ) {
|
||||||
|
$wpdb->query( "INSERT INTO {$wpdb->project_translation_status} (`project_id`, `locale`, `locale_slug`, `all`, `current`, `waiting`, `fuzzy`, `warnings`, `untranslated`, `date_added`, `date_modified` ) VALUES " . implode( ', ', $values ) . " ON DUPLICATE KEY UPDATE `all`=VALUES(`all`), `current`=VALUES(`current`), `waiting`=VALUES(`waiting`), `fuzzy`=VALUES(`fuzzy`), `warnings`=VALUES(`warnings`), `untranslated`=VALUES(`untranslated`), `date_modified`=VALUES(`date_modified`)" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get_schema() : string {
|
||||||
|
global $gp_table_prefix;
|
||||||
|
return <<<"END"
|
||||||
|
CREATE TABLE `{$gp_table_prefix}project_translation_status` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`project_id` int(10) unsigned NOT NULL,
|
||||||
|
`locale` varchar(10) NOT NULL,
|
||||||
|
`locale_slug` varchar(255) NOT NULL,
|
||||||
|
`all` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`current` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`waiting` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`fuzzy` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`warnings` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`untranslated` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `project_locale` (`project_id`,`locale`,`locale_slug`),
|
||||||
|
KEY `all` (`all`),
|
||||||
|
KEY `current` (`current`),
|
||||||
|
KEY `waiting` (`waiting`),
|
||||||
|
KEY `fuzzy` (`fuzzy`),
|
||||||
|
KEY `warnings` (`warnings`),
|
||||||
|
KEY `untranslated` (`untranslated`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||||
|
END;
|
||||||
|
}
|
||||||
|
}
|
241
content/plugins/f-translate/inc/stats/class-userstats.php
Normal file
241
content/plugins/f-translate/inc/stats/class-userstats.php
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* User stats.
|
||||||
|
*
|
||||||
|
* Originally from https://github.com/WordPress/wordpress.org/blob/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-custom-stats/stats/user.php
|
||||||
|
* Used under the GPLv2 license. Copyright 2025 authors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FAIRServer\Translate\Stats;
|
||||||
|
|
||||||
|
use GP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This plugin records the submitted/accepted counts of translations offered by users.
|
||||||
|
*
|
||||||
|
* Only translations with an attached user_id are counted.
|
||||||
|
*
|
||||||
|
* @author dd32
|
||||||
|
*/
|
||||||
|
class UserStats {
|
||||||
|
|
||||||
|
private $user_stats = [];
|
||||||
|
|
||||||
|
private $user_project_stats = [];
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
global $wpdb, $gp_table_prefix;
|
||||||
|
|
||||||
|
add_action( 'gp_translation_created', array( $this, 'translation_updated' ) );
|
||||||
|
add_action( 'gp_translation_saved', array( $this, 'translation_updated' ) );
|
||||||
|
|
||||||
|
// DB Writes are delayed until shutdown to bulk-update the stats during imports.
|
||||||
|
add_action( 'shutdown', array( $this, 'write_stats_to_database' ) );
|
||||||
|
|
||||||
|
$wpdb->user_translations_count = $gp_table_prefix . 'user_translations_count';
|
||||||
|
$wpdb->user_projects = $gp_table_prefix . 'user_projects';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function translation_updated( $translation ) {
|
||||||
|
if ( ! $translation->user_id ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var \GP_Translation_Set */
|
||||||
|
$translation_set = GP::$translation_set->get( $translation->translation_set_id );
|
||||||
|
|
||||||
|
if ( 'waiting' === $translation->status ) {
|
||||||
|
// New translation suggested
|
||||||
|
$this->bump_user_stat( $translation->user_id, $translation_set->locale, $translation_set->slug, 1, 0 );
|
||||||
|
|
||||||
|
} elseif ( 'current' === $translation->status && 'gp_translation_created' === current_filter() ) {
|
||||||
|
// New translation suggested & approved
|
||||||
|
$this->bump_user_stat( $translation->user_id, $translation_set->locale, $translation_set->slug, 1, 1 );
|
||||||
|
|
||||||
|
} elseif ( 'current' === $translation->status ) {
|
||||||
|
// Translation approved
|
||||||
|
$this->bump_user_stat( $translation->user_id, $translation_set->locale, $translation_set->slug, 0, 1 );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record the last time the user submitted a translation for a project/locale.
|
||||||
|
if ( 'gp_translation_created' == current_filter() ) {
|
||||||
|
$project = GP::$project->get( $translation_set->project_id );
|
||||||
|
|
||||||
|
// Find the "root" project ID.
|
||||||
|
// For projects with sub-projects, we only want to store the "parent" project.
|
||||||
|
// ie. wp-plugins/plugin-name, wp/dev, or apps/android
|
||||||
|
$project_path_we_want = implode( '/', array_slice( explode( '/', $project->path ), 0, 2 ) );
|
||||||
|
if ( $project_path_we_want != $project->path ) {
|
||||||
|
$project = GP::$project->by_path( $project_path_we_want );
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = "{$translation->user_id},{$project->id},{$translation_set->locale},{$translation_set->slug}";
|
||||||
|
if ( ! isset( $this->user_project_stats[ $key ] ) ) {
|
||||||
|
$this->user_project_stats[ $key ] = 0;
|
||||||
|
}
|
||||||
|
$this->user_project_stats[ $key ]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function bump_user_stat( $user_id, $locale, $locale_slug, $suggested = 0, $accepted = 0 ) {
|
||||||
|
$key = "$user_id,$locale,$locale_slug";
|
||||||
|
|
||||||
|
if ( isset( $this->user_stats[ $key ] ) ) {
|
||||||
|
$this->user_stats[ $key ]->suggested += $suggested;
|
||||||
|
$this->user_stats[ $key ]->accepted += $accepted;
|
||||||
|
} else {
|
||||||
|
$this->user_stats[ $key ] = (object) array(
|
||||||
|
'suggested' => $suggested,
|
||||||
|
'accepted' => $accepted,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function write_stats_to_database() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$now = current_time( 'mysql', 1 );
|
||||||
|
|
||||||
|
$values = [];
|
||||||
|
foreach ( $this->user_stats as $key => $stats ) {
|
||||||
|
list( $user_id, $locale, $locale_slug ) = explode( ',', $key );
|
||||||
|
|
||||||
|
$values[] = $wpdb->prepare( '(%d, %s, %s, %d, %d, %s, %s)',
|
||||||
|
$user_id,
|
||||||
|
$locale,
|
||||||
|
$locale_slug,
|
||||||
|
$stats->suggested,
|
||||||
|
$stats->accepted,
|
||||||
|
$now,
|
||||||
|
$now
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we're processing a large batch, add them as we go to avoid query lengths & memory limits.
|
||||||
|
if ( count( $values ) > 50 ) {
|
||||||
|
$wpdb->query(
|
||||||
|
"INSERT INTO {$wpdb->user_translations_count} (`user_id`, `locale`, `locale_slug`, `suggested`, `accepted`, `date_added`, `date_modified`)
|
||||||
|
VALUES " . implode( ', ', $values ) . "
|
||||||
|
ON DUPLICATE KEY UPDATE `suggested`=`suggested` + VALUES(`suggested`), `accepted`=`accepted` + VALUES(`accepted`), `date_modified`=VALUES(`date_modified`)"
|
||||||
|
);
|
||||||
|
$values = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $values ) {
|
||||||
|
$wpdb->query(
|
||||||
|
"INSERT INTO {$wpdb->user_translations_count} (`user_id`, `locale`, `locale_slug`, `suggested`, `accepted`, `date_added`, `date_modified`)
|
||||||
|
VALUES " . implode( ', ', $values ) . "
|
||||||
|
ON DUPLICATE KEY UPDATE `suggested`=`suggested` + VALUES(`suggested`), `accepted`=`accepted` + VALUES(`accepted`), `date_modified`=VALUES(`date_modified`)"
|
||||||
|
);
|
||||||
|
$values = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the user project stats too.
|
||||||
|
$values = [];
|
||||||
|
foreach ( $this->user_project_stats as $key => $string_count ) {
|
||||||
|
list( $user_id, $project_id, $locale, $locale_slug ) = explode( ',', $key );
|
||||||
|
|
||||||
|
// Step 1 - Does this user already have the project listed? Just Bump the date.
|
||||||
|
if ( $id = $wpdb->get_var( $wpdb->prepare(
|
||||||
|
"SELECT id FROM {$wpdb->user_projects}
|
||||||
|
WHERE user_id = %d AND project_id = %d AND locale = %s AND locale_slug = %s",
|
||||||
|
$user_id, $project_id, $locale, $locale_slug
|
||||||
|
) ) ) {
|
||||||
|
$wpdb->update(
|
||||||
|
$wpdb->user_projects,
|
||||||
|
[ 'last_modified' => $now ],
|
||||||
|
[ 'id' => $id ]
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2 - More than 5 strings? Import Maybe? Just insert.
|
||||||
|
if ( $string_count >= 5 ) {
|
||||||
|
$wpdb->insert(
|
||||||
|
$wpdb->user_projects,
|
||||||
|
[
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'project_id' => $project_id,
|
||||||
|
'locale' => $locale,
|
||||||
|
'locale_slug' => $locale_slug,
|
||||||
|
'last_modified' => $now
|
||||||
|
]
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3 - If not, find all the sub-project IDs, then all the translation sets, count strings by user.
|
||||||
|
$this_project = GP::$project->get( $project_id );
|
||||||
|
$translation_set_ids = [];
|
||||||
|
|
||||||
|
if ( ! $this_project->active ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $set = GP::$translation_set->by_project_id_slug_and_locale( $project_id, $locale_slug, $locale ) ) {
|
||||||
|
$translation_set_ids[] = (int) $set->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the strings from the sub projects too
|
||||||
|
foreach ( $this_project->sub_projects() as $project ) {
|
||||||
|
if ( ! $project->active ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ( $set = GP::$translation_set->by_project_id_slug_and_locale( $project->id, $locale_slug, $locale ) ) {
|
||||||
|
$translation_set_ids[] = (int) $set->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_translations = GP::$translation->find_many_no_map( [
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'translation_set_id' => $translation_set_ids
|
||||||
|
] );
|
||||||
|
|
||||||
|
if ( $user_translations && count( $user_translations ) >= 5 ) {
|
||||||
|
$wpdb->insert(
|
||||||
|
$wpdb->user_projects,
|
||||||
|
[
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'project_id' => $project_id,
|
||||||
|
'locale' => $locale,
|
||||||
|
'locale_slug' => $locale_slug,
|
||||||
|
'last_modified' => $now
|
||||||
|
]
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get_schema() : string {
|
||||||
|
global $gp_table_prefix;
|
||||||
|
// Note: WordPress uses BIGINT(20) for user_id; GlotPress uses int(10)
|
||||||
|
return <<<"END"
|
||||||
|
CREATE TABLE `{$gp_table_prefix}user_translations_count` (
|
||||||
|
`id` int(10) NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` int(10) unsigned NOT NULL,
|
||||||
|
`locale` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`locale_slug` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`suggested` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`accepted` int(10) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`date_added` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||||
|
`date_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `user_id` (`user_id`,`locale`,`locale_slug`),
|
||||||
|
KEY `locale` (`locale`,`locale_slug`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||||
|
|
||||||
|
CREATE TABLE `{$gp_table_prefix}user_projects` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` int(10) unsigned NOT NULL,
|
||||||
|
`project_id` int(11) unsigned NOT NULL,
|
||||||
|
`locale` varchar(255) NOT NULL,
|
||||||
|
`locale_slug` varchar(255) NOT NULL DEFAULT 'default',
|
||||||
|
`last_modified` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `user_project_locale` (`user_id`,`project_id`,`locale`,`locale_slug`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||||
|
END;
|
||||||
|
}
|
||||||
|
}
|
114
content/plugins/f-translate/inc/stats/class-warningstats.php
Normal file
114
content/plugins/f-translate/inc/stats/class-warningstats.php
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Warning stats.
|
||||||
|
*
|
||||||
|
* Originally from https://github.com/WordPress/wordpress.org/blob/trunk/wordpress.org/public_html/wp-content/plugins/wporg-gp-custom-stats/stats/warning.php
|
||||||
|
* Used under the GPLv2 license. Copyright 2025 authors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FAIRServer\Translate\Stats;
|
||||||
|
|
||||||
|
use GP;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class records translation warnings.
|
||||||
|
*/
|
||||||
|
class WarningStats {
|
||||||
|
|
||||||
|
private $warning_stats = array();
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
global $wpdb, $gp_table_prefix;
|
||||||
|
|
||||||
|
add_action( 'gp_translation_created', array( $this, 'translation_updated' ) );
|
||||||
|
add_action( 'gp_translation_saved', array( $this, 'translation_updated' ) );
|
||||||
|
|
||||||
|
// DB Writes are delayed until shutdown to bulk-update the stats during imports.
|
||||||
|
add_action( 'shutdown', array( $this, 'write_stats_to_database' ) );
|
||||||
|
|
||||||
|
$wpdb->dotorg_translation_warnings = $gp_table_prefix . 'dotorg_translation_warnings';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function translation_updated( $translation ) {
|
||||||
|
if ( ! $translation->warnings ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only want to trigger for strings which are live, or are for consideration.
|
||||||
|
if ( ! in_array( $translation->status, array( 'current', 'waiting' ) ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var \GP_Original */
|
||||||
|
$original = GP::$original->get( $translation->original_id );
|
||||||
|
/** @var \GP_Project */
|
||||||
|
$project = GP::$project->get( $original->project_id );
|
||||||
|
/** @var \GP_Translation_Set */
|
||||||
|
$translation_set = GP::$translation_set->get( $translation->translation_set_id );
|
||||||
|
|
||||||
|
foreach( $translation->warnings as $index => $warnings ) {
|
||||||
|
foreach ( $warnings as $warning_key => $warning ) {
|
||||||
|
$key = "{$translation->user_id},{$translation_set->locale},{$translation_set->slug},{$project->path},{$translation->id},{$warning_key}";
|
||||||
|
|
||||||
|
$this->warning_stats[ $key ] = $warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function write_stats_to_database() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$now = current_time( 'mysql', 1 );
|
||||||
|
|
||||||
|
$values = array();
|
||||||
|
foreach ( $this->warning_stats as $key => $message ) {
|
||||||
|
list( $user_id, $locale, $locale_slug, $project_path, $translation_id, $warning ) = explode( ',', $key );
|
||||||
|
|
||||||
|
$values[] = $wpdb->prepare( '(%d, %s, %s, %s, %d, %s, %s, %s)',
|
||||||
|
$user_id,
|
||||||
|
$locale,
|
||||||
|
$locale_slug,
|
||||||
|
$project_path,
|
||||||
|
$translation_id,
|
||||||
|
$warning,
|
||||||
|
$now,
|
||||||
|
$message
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we're processing a large batch, add them as we go to avoid query lengths & memory limits.
|
||||||
|
if ( count( $values ) > 50 ) {
|
||||||
|
$wpdb->query(
|
||||||
|
"INSERT INTO {$wpdb->dotorg_translation_warnings} (`user_id`, `locale`, `locale_slug`, `project_path`, `translation_id`, `warning`, `timestamp`, `message`)
|
||||||
|
VALUES " . implode( ', ', $values )
|
||||||
|
);
|
||||||
|
$values = array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $values ) {
|
||||||
|
$wpdb->query(
|
||||||
|
"INSERT INTO {$wpdb->dotorg_translation_warnings} (`user_id`, `locale`, `locale_slug`, `project_path`, `translation_id`, `warning`, `timestamp`, `message`)
|
||||||
|
VALUES " . implode( ', ', $values )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get_schema() : string {
|
||||||
|
global $gp_table_prefix;
|
||||||
|
return <<<"END"
|
||||||
|
CREATE TABLE `{$gp_table_prefix}dotorg_translation_warnings` (
|
||||||
|
`id` bigint(10) NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` bigint(20) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`locale` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`locale_slug` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`project_path` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`translation_id` bigint(20) unsigned NOT NULL DEFAULT '0',
|
||||||
|
`warning` varchar(20) NOT NULL DEFAULT '',
|
||||||
|
`timestamp` datetime NOT NULL default '0000-00-00 00:00:00',
|
||||||
|
`message` longtext
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
||||||
|
END;
|
||||||
|
}
|
||||||
|
}
|
54
content/plugins/f-translate/inc/stats/namespace.php
Normal file
54
content/plugins/f-translate/inc/stats/namespace.php
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Custom stats for GlotPress.
|
||||||
|
*
|
||||||
|
* Based on code from https://github.com/WordPress/wordpress.org
|
||||||
|
* Used under the GPLv2 license. Copyright authors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FAIRServer\Translate\Stats;
|
||||||
|
|
||||||
|
use WP_CLI;
|
||||||
|
|
||||||
|
function bootstrap() {
|
||||||
|
add_action( 'gp_init', initialize(...) );
|
||||||
|
|
||||||
|
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||||
|
WP_CLI::add_command( 'fair-translate stats init', __NAMESPACE__ . '\\init_command' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initialize() {
|
||||||
|
$GLOBALS['gp_custom_stats'] = [
|
||||||
|
'user' => new UserStats(),
|
||||||
|
'project' => new ProjectStats(),
|
||||||
|
'warnings' => new WarningStats(),
|
||||||
|
'discarded_warning' => new DiscardedWarningStats(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the stats system, including database tables.
|
||||||
|
*/
|
||||||
|
function init_command( $args, $assoc_args ) {
|
||||||
|
$schemas = [
|
||||||
|
'user' => UserStats::get_schema(),
|
||||||
|
'project' => ProjectStats::get_schema(),
|
||||||
|
'warnings' => WarningStats::get_schema(),
|
||||||
|
'discarded_warning' => DiscardedWarningStats::get_schema(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ( ! function_exists( 'dbDelta' ) ) {
|
||||||
|
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $schemas as $name => $schema ) {
|
||||||
|
$res = dbDelta( $schema );
|
||||||
|
foreach ( $res as $result ) {
|
||||||
|
WP_CLI::line( $result );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output a success message.
|
||||||
|
WP_CLI::success( 'Stats tables initialized.' );
|
||||||
|
}
|
24
content/plugins/f-translate/plugin.php
Normal file
24
content/plugins/f-translate/plugin.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Plugin Name: FAIR Server - Translate
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FAIRServer\Translate;
|
||||||
|
|
||||||
|
use FAIRServer;
|
||||||
|
|
||||||
|
defined( 'WPORGPATH' ) or define( 'WPORGPATH', ABSPATH . '/wp-content/themes/pub/wporg/inc/' );
|
||||||
|
|
||||||
|
// Ported from dotorg.
|
||||||
|
require __DIR__ . '/wporg-gp-routes/wporg-gp-routes.php';
|
||||||
|
|
||||||
|
// Our code.
|
||||||
|
require __DIR__ . '/inc/namespace.php';
|
||||||
|
require __DIR__ . '/inc/stats/namespace.php';
|
||||||
|
require __DIR__ . '/inc/stats/class-discardedwarningstats.php';
|
||||||
|
require __DIR__ . '/inc/stats/class-projectstats.php';
|
||||||
|
require __DIR__ . '/inc/stats/class-warningstats.php';
|
||||||
|
|
||||||
|
FAIRServer\register_class_path( __NAMESPACE__, __DIR__ . '/inc/' );
|
||||||
|
|
||||||
|
bootstrap();
|
12
content/plugins/f-translate/templates/404.php
Normal file
12
content/plugins/f-translate/templates/404.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
gp_title( __( 'Not Found < GlotPress', 'glotpress' ) );
|
||||||
|
|
||||||
|
gp_tmpl_header();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<h2>Page not found</h2>
|
||||||
|
|
||||||
|
<p>The page you were looking for could not be found. I’m sorry, it’s not your fault… probably. <a href="<?php echo esc_url( home_url( '/' ) ); ?>">Return to the homepage</a></p>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
gp_tmpl_footer();
|
219
content/plugins/f-translate/templates/consistency.php
Normal file
219
content/plugins/f-translate/templates/consistency.php
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
<?php
|
||||||
|
gp_title( 'Translation Consistency < GlotPress' );
|
||||||
|
$breadcrumb = array();
|
||||||
|
$breadcrumb[] = gp_link_get( '/', 'Locales' );
|
||||||
|
$breadcrumb[] = 'Translation Consistency';
|
||||||
|
gp_breadcrumb( $breadcrumb );
|
||||||
|
gp_tmpl_header();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<p>Analyze translation consistency across projects. The result is limited to 500 translations.</p>
|
||||||
|
|
||||||
|
|
||||||
|
<form action="/consistency" method="get" class="consistency-form">
|
||||||
|
<p class="consistency-fields">
|
||||||
|
<span class="consistency-field">
|
||||||
|
<label for="original">Original</label>
|
||||||
|
<input id="original" type="text" name="search" required value="<?php echo gp_esc_attr_with_entities( $search ); ?>" class="consistency-form-search" placeholder="Enter original to search for…">
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="consistency-field">
|
||||||
|
<label for="set">Locale</label>
|
||||||
|
<?php
|
||||||
|
$locale_options = [
|
||||||
|
'' => 'Select a locale',
|
||||||
|
];
|
||||||
|
$sets_to_hide = array(
|
||||||
|
'ca/valencia',
|
||||||
|
'nl/formal/default',
|
||||||
|
'en/formal',
|
||||||
|
'en/default',
|
||||||
|
'fr/formal',
|
||||||
|
'de/formal/default',
|
||||||
|
'de-ch/info/default',
|
||||||
|
'pt/ao90/default',
|
||||||
|
'sr/latin',
|
||||||
|
'sr/latin/latin',
|
||||||
|
);
|
||||||
|
$sets = array_diff_key( $sets, array_flip( $sets_to_hide ) );
|
||||||
|
$locale_options = array_merge( $locale_options, $sets );
|
||||||
|
echo gp_select(
|
||||||
|
'set',
|
||||||
|
$locale_options,
|
||||||
|
$set,
|
||||||
|
[
|
||||||
|
'class' => 'consistency-form-locale',
|
||||||
|
'required' => 'required',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="consistency-field">
|
||||||
|
<label for="project">Project</label>
|
||||||
|
<?php
|
||||||
|
$project_options = [
|
||||||
|
'' => 'All Projects',
|
||||||
|
];
|
||||||
|
$project_options = $project_options + $projects;
|
||||||
|
echo gp_select(
|
||||||
|
'project',
|
||||||
|
$project_options,
|
||||||
|
$project,
|
||||||
|
[
|
||||||
|
'class' => 'consistency-form-project',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="search_case_sensitive" value="1"<?php checked( $search_case_sensitive ); ?>>
|
||||||
|
Case Sensitive
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<button type="submit" class="button is-primary consistency-form-submit">Analyze</button>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ( $performed_search && ! $results ) {
|
||||||
|
echo '<div class="notice"><p>No results were found.</p></div>';
|
||||||
|
|
||||||
|
} elseif ( $performed_search && $results ) {
|
||||||
|
$translations_unique_count = count( $translations_unique );
|
||||||
|
$has_different_translations = $translations_unique_count > 1;
|
||||||
|
if ( ! $has_different_translations ) {
|
||||||
|
echo '<div class="notice"><p>All originals have the same translations.</p></div>';
|
||||||
|
} else {
|
||||||
|
echo '<div id="translations-overview" class="notice wporg-notice-warning"><p>There are ' . $translations_unique_count . ' different translations. <a id="toggle-translations-unique" href="#show">View</a></p>';
|
||||||
|
echo '<ul class="translations-unique hidden">';
|
||||||
|
foreach ( $translations_unique_counts as $translation => $count ) {
|
||||||
|
printf(
|
||||||
|
'<li>%s <small>(%s)</small> <a class="anchor-jumper with-tooltip" aria-label="Go to translation" href="#%s">↓</a></li>',
|
||||||
|
str_replace( ' ', '<span class="space"> </span>', esc_translation( $translation ) ),
|
||||||
|
1 === $count ? $count . ' time' : $count . ' times',
|
||||||
|
esc_attr( 't-' . md5( $translation ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo '</ul>';
|
||||||
|
echo '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<table class="gp-table consistency-table">
|
||||||
|
<thead>
|
||||||
|
<th>Original</th>
|
||||||
|
<th>Translation</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
$translations = array_keys( $translations_unique_counts );
|
||||||
|
foreach ( $translations as $translation_index => $translation ) {
|
||||||
|
$prev_arrow = '';
|
||||||
|
$next_arrow = '';
|
||||||
|
|
||||||
|
$prev_translation = $translations[ $translation_index - 1 ] ?? false;
|
||||||
|
$next_translation = $translations[ $translation_index + 1 ] ?? false;
|
||||||
|
|
||||||
|
if ( ! $prev_translation ) {
|
||||||
|
$next_arrow = '<a class="anchor-jumper with-tooltip" aria-label="Go to next translation" href="' . esc_attr( '#t-' . md5( $next_translation ) ) . '">↓</a>';
|
||||||
|
} elseif ( ! $next_translation ) {
|
||||||
|
$prev_arrow = '<a class="anchor-jumper with-tooltip" aria-label="Go to previous translation" href="' . esc_attr( '#t-' . md5( $prev_translation ) ) . '">↑</a>';
|
||||||
|
} else {
|
||||||
|
$prev_arrow = '<a class="anchor-jumper with-tooltip" aria-label="Go to previous translation" href="' . esc_attr( '#t-' . md5( $prev_translation ) ) . '">↑</a>';
|
||||||
|
$next_arrow = '<a class="anchor-jumper with-tooltip" aria-label="Go to next translation" href="' . esc_attr( '#t-' . md5( $next_translation ) ) . '">↓</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<tr id="%s" class="new-translation"><th colspan="2"><strong>%s</strong> %s %s</th></tr>',
|
||||||
|
esc_attr( 't-' . md5( $translation ) ),
|
||||||
|
esc_translation( $translation ),
|
||||||
|
$next_arrow,
|
||||||
|
$prev_arrow
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $results as $result ) {
|
||||||
|
if ( $result->translation != $translation ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$project_name = $result->project_name;
|
||||||
|
$parent_project_id = $result->project_parent_id;
|
||||||
|
$is_active = $result->active;
|
||||||
|
while ( $parent_project_id ) {
|
||||||
|
$parent_project = GP::$project->get( $parent_project_id );
|
||||||
|
$parent_project_id = $parent_project->parent_project_id;
|
||||||
|
$project_name = "{$parent_project->name} - {$project_name}";
|
||||||
|
$is_active = $is_active && $parent_project->active;
|
||||||
|
}
|
||||||
|
|
||||||
|
$original_context = '';
|
||||||
|
if ( $result->original_context ) {
|
||||||
|
$original_context = sprintf(
|
||||||
|
' <span class="context">%s</span>',
|
||||||
|
esc_translation( $result->original_context )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if( $is_active ) {
|
||||||
|
$active_text = '';
|
||||||
|
} else {
|
||||||
|
$active_text = sprintf(
|
||||||
|
' <span class="dashicons dashicons-flag"></span><span class="inactive">%s</span>',
|
||||||
|
esc_translation( '(inactive)' )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<tr class="%s"><td>%s</td><td>%s</td></tr>',
|
||||||
|
isset( $parent_project->name ) ? sanitize_title( 'project-' . $parent_project->name ) : '',
|
||||||
|
sprintf(
|
||||||
|
'<div class="string">%s%s</div>
|
||||||
|
<div class="meta">Project: <a href="/projects/%s/%s/">%s</a>%s</div>',
|
||||||
|
esc_translation( $result->original_singular ),
|
||||||
|
$original_context,
|
||||||
|
$result->project_path,
|
||||||
|
$set,
|
||||||
|
$project_name,
|
||||||
|
$active_text
|
||||||
|
),
|
||||||
|
sprintf(
|
||||||
|
'<div class="string%s">%s</div>
|
||||||
|
<div class="meta">
|
||||||
|
<a href="/projects/%s/%s/?filters[status]=either&filters[original_id]=%d&filters[translation_id]=%d">Source</a> |
|
||||||
|
Added: %s
|
||||||
|
</div>',
|
||||||
|
$locale_is_rtl ? ' rtl' : '',
|
||||||
|
esc_translation( $result->translation ),
|
||||||
|
$result->project_path,
|
||||||
|
$set,
|
||||||
|
$result->original_id,
|
||||||
|
$result->translation_id,
|
||||||
|
$result->translation_added
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
jQuery( document ).ready( function( $ ) {
|
||||||
|
$( '#toggle-translations-unique' ).on( 'click', function( event ) {
|
||||||
|
event.preventDefault();
|
||||||
|
$( '.translations-unique' ).toggleClass( 'hidden' );
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php gp_tmpl_footer();
|
1
content/plugins/f-translate/templates/css/chartist.min.css
vendored
Normal file
1
content/plugins/f-translate/templates/css/chartist.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
5
content/plugins/f-translate/templates/footer.php
Normal file
5
content/plugins/f-translate/templates/footer.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
</article><!-- gp-content -->
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
get_footer();
|
44
content/plugins/f-translate/templates/header.php
Normal file
44
content/plugins/f-translate/templates/header.php
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
gp_enqueue_styles( array( 'gp-jquery-webui-popover', 'driver-js' ) );
|
||||||
|
gp_enqueue_scripts( array( 'gp-tour' ) );
|
||||||
|
|
||||||
|
get_header();
|
||||||
|
|
||||||
|
do_blocks( '<!-- wp:template-part {"slug":"header"} /-->' );
|
||||||
|
|
||||||
|
?>
|
||||||
|
<script type="text/javascript">document.body.className = document.body.className.replace('no-js','js');</script>
|
||||||
|
|
||||||
|
<main class="wp-block-group" style="margin-top:var(--wp--preset--spacing--60)">
|
||||||
|
|
||||||
|
<article class="gp-content clearfix">
|
||||||
|
<header>
|
||||||
|
<h1 class="entry-title">Fair Translations</h1>
|
||||||
|
<p class="entry-subtitle">
|
||||||
|
Contribute to translation WordPress core, themes, and plugins by translating them into your language.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="gp-js-message"></div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ( gp_notice( 'error' ) ) :
|
||||||
|
?>
|
||||||
|
<div class="error">
|
||||||
|
<?php echo gp_notice( 'error' ); //TODO: run kses on notices ?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
|
|
||||||
|
if ( gp_notice() ) :
|
||||||
|
?>
|
||||||
|
<div class="notice">
|
||||||
|
<?php echo gp_notice(); ?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
|
|
||||||
|
echo gp_breadcrumb();
|
||||||
|
|
||||||
|
do_action( 'gp_after_notices' );
|
421
content/plugins/f-translate/templates/helper-functions.php
Normal file
421
content/plugins/f-translate/templates/helper-functions.php
Normal file
|
@ -0,0 +1,421 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
wp_register_style(
|
||||||
|
'wporg-translate',
|
||||||
|
plugins_url( 'style.css', __FILE__ ),
|
||||||
|
[ 'gp-base' ],
|
||||||
|
filemtime( __DIR__ . '/style.css' )
|
||||||
|
);
|
||||||
|
gp_enqueue_style( 'wporg-translate' );
|
||||||
|
|
||||||
|
gp_enqueue_script( 'jquery' );
|
||||||
|
|
||||||
|
wp_register_script(
|
||||||
|
'details-element-polyfill',
|
||||||
|
plugins_url( 'js/details-element-polyfill.min.js', __FILE__ ),
|
||||||
|
[],
|
||||||
|
'2.3.1'
|
||||||
|
);
|
||||||
|
|
||||||
|
gp_enqueue_script( 'details-element-polyfill' );
|
||||||
|
|
||||||
|
wp_register_script(
|
||||||
|
'autosize',
|
||||||
|
plugins_url( 'js/autosize.min.js', __FILE__ ),
|
||||||
|
[],
|
||||||
|
'4.0.2'
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_register_script(
|
||||||
|
'wporg-translate-editor',
|
||||||
|
plugins_url( 'js/editor.js', __FILE__ ),
|
||||||
|
[ 'gp-editor', 'autosize' ],
|
||||||
|
filemtime( __DIR__ . '/js/editor.js' )
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_localize_script(
|
||||||
|
'wporg-translate-editor',
|
||||||
|
'wporgEditorSettings',
|
||||||
|
array(
|
||||||
|
'nonce' => wp_create_nonce( 'wporg-editor-settings' ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_register_style(
|
||||||
|
'chartist',
|
||||||
|
plugins_url( 'css/chartist.min.css', __FILE__ ),
|
||||||
|
[],
|
||||||
|
'0.9.5'
|
||||||
|
);
|
||||||
|
wp_register_script(
|
||||||
|
'chartist',
|
||||||
|
plugins_url( 'js/chartist.min.js', __FILE__ ),
|
||||||
|
[],
|
||||||
|
'0.9.5'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( isset( $template ) && 'translations' === $template ) {
|
||||||
|
gp_enqueue_script( 'wporg-translate-editor' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove Emoji fallback support
|
||||||
|
remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
|
||||||
|
remove_action( 'wp_print_styles', 'print_emoji_styles' );
|
||||||
|
|
||||||
|
// Remove output of hreflang tags.
|
||||||
|
add_action( 'wp_head', function () {
|
||||||
|
remove_action( 'wp_head', 'WordPressdotorg\Theme\hreflang_link_attributes' );
|
||||||
|
}, 1 );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the document title to that of GlotPress.
|
||||||
|
*
|
||||||
|
* @see https://github.com/GlotPress/GlotPress-WP/issues/8
|
||||||
|
*/
|
||||||
|
add_filter( 'document_title_parts', static function() {
|
||||||
|
return [
|
||||||
|
'title' => gp_title(),
|
||||||
|
];
|
||||||
|
}, 1 );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints markup for translations help modal in footer.
|
||||||
|
*/
|
||||||
|
add_action( 'gp_footer', static function() use ( $template, $args ) {
|
||||||
|
if ( 'translations' === $template && ! empty( $args['locale_slug'] ) ) {
|
||||||
|
$locale = GP_Locales::by_slug( $args['locale_slug'] );
|
||||||
|
wporg_translation_help_modal( $locale );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints markup for the translation help dialog.
|
||||||
|
*
|
||||||
|
* @param \GP_Locale $locale Locale of the current translation set.
|
||||||
|
*/
|
||||||
|
function wporg_translation_help_modal( $locale ) {
|
||||||
|
$locale_resources = wporg_get_locale_resources( $locale )
|
||||||
|
?>
|
||||||
|
<div id="wporg-translation-help-modal" class="wporg-translate-modal">
|
||||||
|
<div class="wporg-translate-modal__overlay">
|
||||||
|
<div class="wporg-translate-modal__frame" role="dialog" aria-labelledby="wporg-translation-help-modal-headline">
|
||||||
|
<div class="wporg-translate-modal__header">
|
||||||
|
<h1 id="wporg-translation-help-modal-headline" class="wporg-translate-modal__headline">Help</h1>
|
||||||
|
<button type="button" aria-label="Close modal" class="wporg-translate-modal__close"><span class="screen-reader-text">Close</span><span aria-hidden="true" class="dashicons dashicons-no-alt"></span></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wporg-translate-modal__content">
|
||||||
|
<div class="wporg-translate-modal__content_primary">
|
||||||
|
<h2>Validating Translations</h2>
|
||||||
|
<p>After a contributor suggests a string, the string gets a status of “suggested”. In order to transform them into “approved” strings (which will be used in WordPress), a Translation Editor needs to accept (or reject) those suggestions.<br>
|
||||||
|
Please give editors a reasonable time to validate suggestions — We are all volunteers.</p>
|
||||||
|
|
||||||
|
<h2>Translation Status</h2>
|
||||||
|
<ul class="no-list">
|
||||||
|
<li><span style="color: #c1e1b9">◼︎</span> <strong>Current:</strong> Indicates an approved string.</li>
|
||||||
|
<li><span style="color: #ffe399">◼︎</span> <strong>Waiting:</strong> Indicates a string that was suggested, but not yet approved by a Translation Editor.</li>
|
||||||
|
<li><span style="color: #fbc5a9">◼︎</span> <strong>Fuzzy:</strong> Indicates a “fuzzy” string. Those translations need to be reviewed for accuracy and edited or approved.</li>
|
||||||
|
<li><span style="color: #cdc5e1">◼︎</span> <strong>Old:</strong> Indicates a string that was obsoleted by a newer, approved translation.</li>
|
||||||
|
<li><span style="color: #dc3232">◼︎</span> <strong>Warning:</strong> Indicates validation warnings. These translations either need to be corrected or their warnings explicitly discarded by a Translation Editor.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="wporg-translate-modal__content_secondary">
|
||||||
|
<?php if ( $locale_resources ) : ?>
|
||||||
|
<h2><span aria-hidden="true" class="dashicons dashicons-info"></span> Locale Resources for <?php echo esc_html( $locale->english_name ); ?></h2>
|
||||||
|
<?php echo wp_kses_post( $locale_resources ); ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<h2><span aria-hidden="true" class="dashicons dashicons-admin-page"></span> Global Handbook Resources</h2>
|
||||||
|
<?php
|
||||||
|
add_filter( 'walker_nav_menu_start_el', 'wporg_add_nav_description', 10, 4 );
|
||||||
|
wp_nav_menu( [
|
||||||
|
'theme_location' => 'wporg_translate_global_resources',
|
||||||
|
'fallback_cb' => false,
|
||||||
|
'container' => false,
|
||||||
|
'menu_id' => 'global-resources',
|
||||||
|
'depth' => 2,
|
||||||
|
] );
|
||||||
|
remove_filter( 'walker_nav_menu_start_el', 'wporg_add_nav_description' );
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds descriptions to navigation items.
|
||||||
|
*
|
||||||
|
* @param string $item_output The menu item output.
|
||||||
|
* @param WP_Post $item Menu item object.
|
||||||
|
* @param int $depth Depth of the menu.
|
||||||
|
* @param array $args wp_nav_menu() arguments.
|
||||||
|
* @return string Menu item with possible description.
|
||||||
|
*/
|
||||||
|
function wporg_add_nav_description( $item_output, $item, $depth, $args ) {
|
||||||
|
if ( $item->description ) {
|
||||||
|
$item_output = str_replace(
|
||||||
|
$args->link_after . '</a>',
|
||||||
|
$args->link_after . '</a>: <span class="menu-item-description">' . $item->description . '</span>',
|
||||||
|
$item_output
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item_output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the navigation menu from the assigned Rosetta site.
|
||||||
|
*
|
||||||
|
* Output is cached for an hour.
|
||||||
|
*
|
||||||
|
* @param \GP_Locale $locale
|
||||||
|
* @return string The HTML markup of the navigation menu.
|
||||||
|
*/
|
||||||
|
function wporg_get_locale_resources( $locale ) {
|
||||||
|
if ( empty( $locale->wp_locale ) ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$transient_key = 'locale-resources-' . $locale->wp_locale;
|
||||||
|
$cache = get_transient( $transient_key );
|
||||||
|
|
||||||
|
if ( false !== $cache ) {
|
||||||
|
return $cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = get_sites( [
|
||||||
|
'locale' => $locale->wp_locale,
|
||||||
|
'network_id' => WPORG_GLOBAL_NETWORK_ID,
|
||||||
|
'path' => '/',
|
||||||
|
'fields' => 'ids',
|
||||||
|
'number' => '1',
|
||||||
|
] );
|
||||||
|
$site_id = array_shift( $result );
|
||||||
|
if ( ! $site_id ) {
|
||||||
|
set_transient( $transient_key, '', HOUR_IN_SECONDS );
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch_to_blog( $site_id );
|
||||||
|
|
||||||
|
add_filter( 'walker_nav_menu_start_el', 'wporg_add_nav_description', 10, 4 );
|
||||||
|
|
||||||
|
$menu = wp_nav_menu( [
|
||||||
|
'theme_location' => 'rosetta_translation_contributor_resources',
|
||||||
|
'fallback_cb' => false,
|
||||||
|
'container' => false,
|
||||||
|
'menu_id' => 'locale-resources',
|
||||||
|
'depth' => 2,
|
||||||
|
'echo' => false,
|
||||||
|
] );
|
||||||
|
|
||||||
|
remove_filter( 'walker_nav_menu_start_el', 'wporg_add_nav_description' );
|
||||||
|
|
||||||
|
restore_current_blog();
|
||||||
|
|
||||||
|
set_transient( $transient_key, $menu, HOUR_IN_SECONDS );
|
||||||
|
|
||||||
|
return $menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds custom classes to the array of body classes.
|
||||||
|
*
|
||||||
|
* @param array $classes An array of body classes.
|
||||||
|
* @return array Filtered body classes.
|
||||||
|
*/
|
||||||
|
function wporg_gp_template_body_classes( $classes ) {
|
||||||
|
$classes[] = 'no-js';
|
||||||
|
return $classes;
|
||||||
|
}
|
||||||
|
add_filter( 'body_class', 'wporg_gp_template_body_classes' );
|
||||||
|
|
||||||
|
add_action( 'gp_tmpl_load_locations', function( $locations, $template, $args, $template_path ) {
|
||||||
|
$core_templates = GP_PATH . 'gp-templates/';
|
||||||
|
require_once $core_templates . 'helper-functions.php';
|
||||||
|
$locations[] = $core_templates;
|
||||||
|
return $locations;
|
||||||
|
}, 50, 4 );
|
||||||
|
|
||||||
|
function wporg_gp_is_index() {
|
||||||
|
return GP::$current_route instanceof \WordPressdotorg\GlotPress\Routes\Routes\Index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints JavaScript helper for menu toggle.
|
||||||
|
*/
|
||||||
|
add_action( 'gp_footer', function() {
|
||||||
|
?>
|
||||||
|
<script>
|
||||||
|
( function( $ ) {
|
||||||
|
$( function() {
|
||||||
|
var $menu = $( '#site-navigation' );
|
||||||
|
|
||||||
|
if ( $menu.length ) {
|
||||||
|
$menu.find( 'button' ).on( 'click', function() {
|
||||||
|
$menu.toggleClass( 'toggled' );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} )( jQuery );
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
} );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints HTML markup for translation textareas.
|
||||||
|
*
|
||||||
|
* @param object $entry Current translation entry.
|
||||||
|
* @param array $permissions User's permissions.
|
||||||
|
* @param int $index Plural index.
|
||||||
|
*/
|
||||||
|
function wporg_gp_translate_textarea( $entry, $permissions, $index = 0 ) {
|
||||||
|
list( $can_edit, $can_approve ) = $permissions;
|
||||||
|
$disabled = $can_edit ? '' : 'disabled="disabled"';
|
||||||
|
?>
|
||||||
|
<div class="textareas<?php echo ( 0 === $index ) ? ' active' : ''; ?>" data-plural-index="<?php echo $index; ?>">
|
||||||
|
<?php
|
||||||
|
if ( isset( $entry->warnings[ $index ] ) ) :
|
||||||
|
$warnings = $entry->warnings[ $index ];
|
||||||
|
foreach ( $warnings as $key => $value ) :
|
||||||
|
?>
|
||||||
|
<div class="warning secondary">
|
||||||
|
<strong><?php _e( 'Warning:', 'glotpress' ); ?></strong> <?php echo esc_html( $value ); ?>
|
||||||
|
|
||||||
|
<?php if ( $can_approve ) : ?>
|
||||||
|
<a href="#" class="discard-warning" data-nonce="<?php echo esc_attr( wp_create_nonce( 'discard-warning_' . $index . $key ) ); ?>" data-key="<?php echo esc_attr( $key ); ?>" data-index="<?php echo esc_attr( $index ); ?>"><?php _e( 'Discard', 'glotpress' ); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
endforeach;
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
<textarea placeholder="Enter translation here" class="foreign-text" name="translation[<?php echo esc_attr( $entry->original_id ); ?>][]" id="translation_<?php echo esc_attr( $entry->original_id ); ?>_<?php echo esc_attr( $index ); ?>" <?php echo $disabled; // WPCS: XSS ok. ?>><?php echo esc_translation( gp_array_get( $entry->translations, $index ) ); // WPCS: XSS ok. ?></textarea>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the number of warnings.
|
||||||
|
*
|
||||||
|
* @param object $entry Current translation entry.
|
||||||
|
* @return int Number of warnings.
|
||||||
|
*/
|
||||||
|
function wporg_gp_count_warnings( $entry ) {
|
||||||
|
$count = 0;
|
||||||
|
|
||||||
|
if ( empty( $entry->warnings ) ) {
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $entry->warnings as $warnings ) {
|
||||||
|
$count += count( $warnings );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists file references for a translation.
|
||||||
|
*
|
||||||
|
* @param \GP_Project $project Current project.
|
||||||
|
* @param object $entry Current translation entry.
|
||||||
|
*/
|
||||||
|
function wporg_references( $project, $entry ) {
|
||||||
|
?>
|
||||||
|
<ul>
|
||||||
|
<?php
|
||||||
|
foreach ( $entry->references as $reference ) :
|
||||||
|
list( $file, $line ) = array_pad( explode( ':', $reference ), 2, 0 );
|
||||||
|
if ( $source_url = $project->source_url( $file, $line ) ) :
|
||||||
|
?>
|
||||||
|
<li><a target="_blank" href="<?php echo $source_url; ?>"><?php echo $file.':'.$line ?></a></li>
|
||||||
|
<?php
|
||||||
|
elseif ( wp_http_validate_url( $reference ) ) :
|
||||||
|
?>
|
||||||
|
<li><a target="_blank" href="<?php echo esc_url( $reference ); ?>"><?php echo esc_html( $reference ); ?></a></li>
|
||||||
|
<?php
|
||||||
|
else :
|
||||||
|
echo "<li>$file:$line</li>";
|
||||||
|
endif;
|
||||||
|
endforeach;
|
||||||
|
?>
|
||||||
|
</ul>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the URL reference for wordpress-org wporg-mu-plugin file locations.
|
||||||
|
*
|
||||||
|
* @param string $source_url
|
||||||
|
* @param \GP_Project $project
|
||||||
|
* @param string $file
|
||||||
|
* @param string $line
|
||||||
|
*
|
||||||
|
* @return Source URL.
|
||||||
|
*/
|
||||||
|
function wporg_references_wordpress_org_github( $source_url, $project, $file, $line ) {
|
||||||
|
if ( 'meta/wordpress-org' === $project->path ) {
|
||||||
|
// wporg-mu-plugins is mu-plugins/ based, but NOT those in mu-plugins/pub
|
||||||
|
if ( str_starts_with( $file, 'mu-plugins/' ) && ! str_starts_with( $file, 'mu-plugins/pub/' ) ) {
|
||||||
|
$source_url = "https://github.com/WordPress/wporg-mu-plugins/blob/trunk/{$file}#L{$line}";
|
||||||
|
|
||||||
|
// wporg-gutenberg theme is pretty unique path..
|
||||||
|
} elseif ( str_contains( $file, '/themes/wporg-gutenberg/' ) ) {
|
||||||
|
$source_url = "https://github.com/WordPress/wporg-gutenberg/blob/trunk/{$file}#L{$line}";
|
||||||
|
}
|
||||||
|
|
||||||
|
} elseif ( 'meta/rosetta' === $project->path ) {
|
||||||
|
|
||||||
|
// For Rosetta plugins, they're stored in the WordPress.org svn path.
|
||||||
|
if ( str_starts_with( $file, 'plugins/' ) && str_contains( $source_url, 'meta.trac.wordpress.org' ) ) {
|
||||||
|
$source_url = str_replace( '/global.wordpress.org/', '/wordpress.org/', $source_url );
|
||||||
|
|
||||||
|
// Not all Rosetta mu-plugins are public, don't attempt to link to them.
|
||||||
|
} elseif(
|
||||||
|
str_starts_with( $file, 'mu-plugins/' ) &&
|
||||||
|
! (
|
||||||
|
str_starts_with( $file, 'mu-plugins/downloads/' ) ||
|
||||||
|
str_starts_with( $file, 'mu-plugins/roles/' ) ||
|
||||||
|
str_starts_with( $file, 'mu-plugins/showcase/' )
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
$source_url = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $source_url;
|
||||||
|
}
|
||||||
|
add_filter( 'gp_reference_source_url', 'wporg_references_wordpress_org_github', 10, 4 );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to show the context or not.
|
||||||
|
*
|
||||||
|
* Prevents displaying the context if it doesn't provide any new information
|
||||||
|
* to the translator.
|
||||||
|
* Especially for mobile projects the context is mostly a duplicate of the singular string.
|
||||||
|
*
|
||||||
|
* @param \Translation_Entry $translation Current translation entry.
|
||||||
|
* @return bool Whether to show the context or not.
|
||||||
|
*/
|
||||||
|
function wporg_gp_should_display_original_context( $translation ) {
|
||||||
|
// No context available.
|
||||||
|
if ( ! $translation->context ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context is the same as the singular.
|
||||||
|
if ( $translation->singular === $translation->context ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context was cut-off due to VARCHAR(255) in the database schema.
|
||||||
|
if ( 255 === mb_strlen( $translation->context ) && 0 === strpos( $translation->singular, $translation->context ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
BIN
content/plugins/f-translate/templates/images/browsehappy.png
Normal file
BIN
content/plugins/f-translate/templates/images/browsehappy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
content/plugins/f-translate/templates/images/glotpress.png
Normal file
BIN
content/plugins/f-translate/templates/images/glotpress.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
content/plugins/f-translate/templates/images/openverse.png
Normal file
BIN
content/plugins/f-translate/templates/images/openverse.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
80
content/plugins/f-translate/templates/index-locales.php
Normal file
80
content/plugins/f-translate/templates/index-locales.php
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
gp_title( __('Locales < GlotPress') );
|
||||||
|
gp_enqueue_script('common');
|
||||||
|
gp_tmpl_header();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="filter-header">
|
||||||
|
<ul class="filter-header-links">
|
||||||
|
<li><span class="current"><?php _e( 'Find your locale' ); ?></span></li>
|
||||||
|
<li><a href="/stats"><?php _e( 'Stats' ); ?></a></li>
|
||||||
|
<li><a href="/consistency"><?php _e( 'Consistency' ); ?></a></li>
|
||||||
|
</ul>
|
||||||
|
<div class="search-form">
|
||||||
|
<label class="screen-reader-text" for="locales-filter"><?php esc_attr_e( 'Search locales...' ); ?></label>
|
||||||
|
<input placeholder="<?php esc_attr_e( 'Search locales...' ); ?>" type="search" id="locales-filter" class="filter-search">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="intro">If your locale isn’t below, follow the steps in the <a href="https://make.wordpress.org/polyglots/handbook/translating/requesting-a-new-locale/">Translator Handbook</a> to contribute a new locale.</p>
|
||||||
|
|
||||||
|
<div id="locales" class="locales">
|
||||||
|
<?php foreach ( $locales as $locale ) :
|
||||||
|
$percent_complete = 0;
|
||||||
|
if ( isset( $translation_status[ $locale->slug ] ) ) {
|
||||||
|
$status = $translation_status[ $locale->slug ];
|
||||||
|
$percent_complete = floor( $status->current_count / $status->all_count * 100 );
|
||||||
|
}
|
||||||
|
|
||||||
|
$wp_locale = ( isset( $locale->wp_locale ) ) ? $locale->wp_locale : $locale->slug;
|
||||||
|
?>
|
||||||
|
<div class="locale <?php echo 'percent-' . $percent_complete; ?>">
|
||||||
|
<ul class="name">
|
||||||
|
<li class="english"><?php echo gp_link_get( gp_url_join( '/locale', $locale->slug ), $locale->english_name ) ?></li>
|
||||||
|
<li class="native"><?php echo gp_link_get( gp_url_join( '/locale', $locale->slug ), $locale->native_name ) ?></li>
|
||||||
|
<li class="code"><?php echo gp_link_get( gp_url_join( '/locale', $locale->slug ), $wp_locale ) ?></li>
|
||||||
|
</ul>
|
||||||
|
<div class="contributors">
|
||||||
|
<?php
|
||||||
|
$contributors = sprintf(
|
||||||
|
'<span class="dashicons dashicons-admin-users"></span><br />%s',
|
||||||
|
isset( $contributors_count[ $locale->slug ] ) ? $contributors_count[ $locale->slug ] : 0
|
||||||
|
);
|
||||||
|
echo gp_link_get( 'https://make.wordpress.org/polyglots/teams/?locale=' . $locale->wp_locale, $contributors );
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<div class="percent">
|
||||||
|
<div class="percent-complete" style="width:<?php echo $percent_complete; ?>%;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="locale-button">
|
||||||
|
<?php echo gp_link_get( gp_url_join( '/locale', $locale->slug ), 'Contribute Translation', [ 'class' => 'button contribute-button' ] ); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
jQuery( document ).ready( function( $ ) {
|
||||||
|
$rows = $( '#locales' ).find( '.locale' );
|
||||||
|
$( '#locales-filter' ).on( 'input keyup',function() {
|
||||||
|
var words = this.value.toLowerCase().split( ' ' );
|
||||||
|
|
||||||
|
if ( '' === this.value.trim() ) {
|
||||||
|
$rows.show();
|
||||||
|
} else {
|
||||||
|
$rows.hide();
|
||||||
|
$rows.filter( function( i, v ) {
|
||||||
|
var $t = $(this).find( '.name' );
|
||||||
|
for ( var d = 0; d < words.length; ++d ) {
|
||||||
|
if ( $t.text().toLowerCase().indexOf( words[d] ) != -1 ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php gp_tmpl_footer();
|
6
content/plugins/f-translate/templates/js/autosize.min.js
vendored
Normal file
6
content/plugins/f-translate/templates/js/autosize.min.js
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/*!
|
||||||
|
autosize 4.0.2
|
||||||
|
license: MIT
|
||||||
|
http://www.jacklmoore.com/autosize
|
||||||
|
*/
|
||||||
|
!function(e,t){if("function"==typeof define&&define.amd)define(["module","exports"],t);else if("undefined"!=typeof exports)t(module,exports);else{var n={exports:{}};t(n,n.exports),e.autosize=n.exports}}(this,function(e,t){"use strict";var n,o,p="function"==typeof Map?new Map:(n=[],o=[],{has:function(e){return-1<n.indexOf(e)},get:function(e){return o[n.indexOf(e)]},set:function(e,t){-1===n.indexOf(e)&&(n.push(e),o.push(t))},delete:function(e){var t=n.indexOf(e);-1<t&&(n.splice(t,1),o.splice(t,1))}}),c=function(e){return new Event(e,{bubbles:!0})};try{new Event("test")}catch(e){c=function(e){var t=document.createEvent("Event");return t.initEvent(e,!0,!1),t}}function r(r){if(r&&r.nodeName&&"TEXTAREA"===r.nodeName&&!p.has(r)){var e,n=null,o=null,i=null,d=function(){r.clientWidth!==o&&a()},l=function(t){window.removeEventListener("resize",d,!1),r.removeEventListener("input",a,!1),r.removeEventListener("keyup",a,!1),r.removeEventListener("autosize:destroy",l,!1),r.removeEventListener("autosize:update",a,!1),Object.keys(t).forEach(function(e){r.style[e]=t[e]}),p.delete(r)}.bind(r,{height:r.style.height,resize:r.style.resize,overflowY:r.style.overflowY,overflowX:r.style.overflowX,wordWrap:r.style.wordWrap});r.addEventListener("autosize:destroy",l,!1),"onpropertychange"in r&&"oninput"in r&&r.addEventListener("keyup",a,!1),window.addEventListener("resize",d,!1),r.addEventListener("input",a,!1),r.addEventListener("autosize:update",a,!1),r.style.overflowX="hidden",r.style.wordWrap="break-word",p.set(r,{destroy:l,update:a}),"vertical"===(e=window.getComputedStyle(r,null)).resize?r.style.resize="none":"both"===e.resize&&(r.style.resize="horizontal"),n="content-box"===e.boxSizing?-(parseFloat(e.paddingTop)+parseFloat(e.paddingBottom)):parseFloat(e.borderTopWidth)+parseFloat(e.borderBottomWidth),isNaN(n)&&(n=0),a()}function s(e){var t=r.style.width;r.style.width="0px",r.offsetWidth,r.style.width=t,r.style.overflowY=e}function u(){if(0!==r.scrollHeight){var e=function(e){for(var t=[];e&&e.parentNode&&e.parentNode instanceof Element;)e.parentNode.scrollTop&&t.push({node:e.parentNode,scrollTop:e.parentNode.scrollTop}),e=e.parentNode;return t}(r),t=document.documentElement&&document.documentElement.scrollTop;r.style.height="",r.style.height=r.scrollHeight+n+"px",o=r.clientWidth,e.forEach(function(e){e.node.scrollTop=e.scrollTop}),t&&(document.documentElement.scrollTop=t)}}function a(){u();var e=Math.round(parseFloat(r.style.height)),t=window.getComputedStyle(r,null),n="content-box"===t.boxSizing?Math.round(parseFloat(t.height)):r.offsetHeight;if(n<e?"hidden"===t.overflowY&&(s("scroll"),u(),n="content-box"===t.boxSizing?Math.round(parseFloat(window.getComputedStyle(r,null).height)):r.offsetHeight):"hidden"!==t.overflowY&&(s("hidden"),u(),n="content-box"===t.boxSizing?Math.round(parseFloat(window.getComputedStyle(r,null).height)):r.offsetHeight),i!==n){i=n;var o=c("autosize:resized");try{r.dispatchEvent(o)}catch(e){}}}}function i(e){var t=p.get(e);t&&t.destroy()}function d(e){var t=p.get(e);t&&t.update()}var l=null;"undefined"==typeof window||"function"!=typeof window.getComputedStyle?((l=function(e){return e}).destroy=function(e){return e},l.update=function(e){return e}):((l=function(e,t){return e&&Array.prototype.forEach.call(e.length?e:[e],function(e){return r(e)}),e}).destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],i),e},l.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],d),e}),t.default=l,e.exports=t.default});
|
13
content/plugins/f-translate/templates/js/chartist.min.js
vendored
Normal file
13
content/plugins/f-translate/templates/js/chartist.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
content/plugins/f-translate/templates/js/details-element-polyfill.min.js
vendored
Normal file
5
content/plugins/f-translate/templates/js/details-element-polyfill.min.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/*
|
||||||
|
Details Element Polyfill 2.3.1
|
||||||
|
Copyright © 2019 Javan Makhmali
|
||||||
|
*/
|
||||||
|
!function(){"use strict";var t=document.createElement("details");t.innerHTML="<summary>a</summary>b",t.setAttribute("style","position: absolute; left: -9999px");var e="open"in t&&function(){(document.body||document.documentElement).appendChild(t);var e=t.offsetHeight;t.open=!0;var n=t.offsetHeight;return t.parentNode.removeChild(t),e!=n}(),n="ontoggle"in t;var r,i,o,a,u='\ndetails, summary {\n display: block;\n}\ndetails:not([open]) > *:not(summary) {\n display: none;\n}\nsummary::before {\n content: "►";\n padding-right: 0.3rem;\n font-size: 0.6rem;\n cursor: default;\n}\n[open] > summary::before {\n content: "▼";\n}\n',s=[],c=s.forEach,d=s.slice;function m(t){(function(t,e){return(t.tagName==e?[t]:[]).concat("function"==typeof t.getElementsByTagName?d.call(t.getElementsByTagName(e)):[])})(t,"SUMMARY").forEach(function(t){var e=b(t,"DETAILS");t.setAttribute("aria-expanded",e.hasAttribute("open")),t.hasAttribute("tabindex")||t.setAttribute("tabindex","0"),t.hasAttribute("role")||t.setAttribute("role","button")})}function l(t){return!(t.defaultPrevented||t.ctrlKey||t.metaKey||t.shiftKey||t.target.isContentEditable)}function f(t){addEventListener("click",function(e){if(l(e)&&e.which<=1){var n=b(e.target,"SUMMARY");n&&n.parentNode&&"DETAILS"==n.parentNode.tagName&&t(n.parentNode)}},!1),addEventListener("keydown",function(e){if(l(e)&&(13==e.keyCode||32==e.keyCode)){var n=b(e.target,"SUMMARY");n&&n.parentNode&&"DETAILS"==n.parentNode.tagName&&(t(n.parentNode),e.preventDefault())}},!1)}function p(t){var e=document.createEvent("Event");e.initEvent("toggle",!1,!1),t.dispatchEvent(e)}function b(t,e){if("function"==typeof t.closest)return t.closest(e);for(;t;){if(t.tagName==e)return t;t=t.parentNode}}e||(document.head.insertAdjacentHTML("afterbegin","<style>"+u+"</style>"),r=document.createElement("details").constructor.prototype,i=r.setAttribute,o=r.removeAttribute,a=Object.getOwnPropertyDescriptor(r,"open"),Object.defineProperties(r,{open:{get:function(){return"DETAILS"==this.tagName?this.hasAttribute("open"):a&&a.get?a.get.call(this):void 0},set:function(t){return"DETAILS"==this.tagName?t?this.setAttribute("open",""):this.removeAttribute("open"):a&&a.set?a.set.call(this,t):void 0}},setAttribute:{value:function(t,e){var n=this,r=function(){return i.call(n,t,e)};if("open"==t&&"DETAILS"==this.tagName){var o=this.hasAttribute("open"),a=r();if(!o){var u=this.querySelector("summary");u&&u.setAttribute("aria-expanded",!0),p(this)}return a}return r()}},removeAttribute:{value:function(t){var e=this,n=function(){return o.call(e,t)};if("open"==t&&"DETAILS"==this.tagName){var r=this.hasAttribute("open"),i=n();if(r){var a=this.querySelector("summary");a&&a.setAttribute("aria-expanded",!1),p(this)}return i}return n()}}}),f(function(t){t.hasAttribute("open")?t.removeAttribute("open"):t.setAttribute("open","")}),m(document),window.MutationObserver?new MutationObserver(function(t){c.call(t,function(t){c.call(t.addedNodes,m)})}).observe(document.documentElement,{subtree:!0,childList:!0}):document.addEventListener("DOMNodeInserted",function(t){m(t.target)})),e&&!n&&(window.MutationObserver?new MutationObserver(function(t){c.call(t,function(t){var e=t.target,n=t.attributeName;"DETAILS"==e.tagName&&"open"==n&&p(e)})}).observe(document.documentElement,{attributes:!0,subtree:!0}):f(function(t){var e=t.getAttribute("open");setTimeout(function(){var n=t.getAttribute("open");e!=n&&p(t)},1)}))}();
|
345
content/plugins/f-translate/templates/js/editor.js
Normal file
345
content/plugins/f-translate/templates/js/editor.js
Normal file
|
@ -0,0 +1,345 @@
|
||||||
|
/* global $gp */
|
||||||
|
( function( $ ){
|
||||||
|
var $html = $( 'html' );
|
||||||
|
var $document = $( document );
|
||||||
|
|
||||||
|
function checkStorage() {
|
||||||
|
var test = 'test',
|
||||||
|
result = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem( 'test', test );
|
||||||
|
result = window.localStorage.getItem( 'test' ) === test;
|
||||||
|
window.localStorage.removeItem( 'test' );
|
||||||
|
} catch(e) {}
|
||||||
|
|
||||||
|
hasStorage = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasStorage = checkStorage();
|
||||||
|
|
||||||
|
// Handle tab view for plural forms.
|
||||||
|
function switchPluralTabs() {
|
||||||
|
var $tab = $( this );
|
||||||
|
if ( $tab.hasClass( 'translation-form-list__tab--active' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var $translationWrapper = $gp.editor.current.find( '.translation-wrapper' );
|
||||||
|
var $formWrapper = $translationWrapper.find( '.translation-form-wrapper' );
|
||||||
|
var $tabList = $formWrapper.find( '.translation-form-list' );
|
||||||
|
var $tabs = $translationWrapper.find( '.textareas' );
|
||||||
|
|
||||||
|
$tabList.find( '.translation-form-list__tab--active' ).removeClass( 'translation-form-list__tab--active' );
|
||||||
|
$tab.addClass( 'translation-form-list__tab--active' );
|
||||||
|
|
||||||
|
$tabs.removeClass( 'active' );
|
||||||
|
|
||||||
|
var index = $tab.data( 'plural-index' );
|
||||||
|
$tabs.filter( '[data-plural-index="' + index + '"]').addClass( 'active' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open menu for contextual links.
|
||||||
|
function toggleLinkMenu() {
|
||||||
|
var $toggle = $( this );
|
||||||
|
var $menu = $toggle.parent( '.button-menu' );
|
||||||
|
|
||||||
|
$menu.toggleClass( 'active' );
|
||||||
|
$document.off( 'click.menu-toggle' );
|
||||||
|
|
||||||
|
if ( $menu.hasClass( 'active' ) ) {
|
||||||
|
$document.on( 'click.menu-toggle', function( event ) {
|
||||||
|
if ( ! $menu.is( event.target ) && 0 === $menu.has( event.target ).length ) {
|
||||||
|
$menu.removeClass( 'active' );
|
||||||
|
$document.off( 'click.menu-toggle' );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatically adjust textarea height to fit text.
|
||||||
|
function textareaAutosize() {
|
||||||
|
var $textarea = $( this );
|
||||||
|
if ( $textarea.hasClass( 'autosize' ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$textarea.addClass( 'autosize' );
|
||||||
|
|
||||||
|
autosize( $textarea );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Prefilter ajax requests to add translation_source to the request.
|
||||||
|
$.ajaxPrefilter( function ( options ) {
|
||||||
|
let data = Object.fromEntries( new URLSearchParams( options.data ) );
|
||||||
|
|
||||||
|
if ( 'POST' === options.type && $gp_editor_options.url === options.url ) {
|
||||||
|
options.data += '&translation_source=frontend';
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Override functions to adopt custom markup.
|
||||||
|
$gp.editor.copy = function() {
|
||||||
|
var $activeTextarea = $gp.editor.current.find( '.textareas.active textarea' );
|
||||||
|
if ( ! $activeTextarea.length ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunks = $activeTextarea.attr( 'id' ).split( '_' );
|
||||||
|
var originalIndex = parseInt( chunks[ chunks.length - 1 ], 10 );
|
||||||
|
|
||||||
|
var $original;
|
||||||
|
if ( 0 === originalIndex ) {
|
||||||
|
$original = $gp.editor.current.find( '.source-string__singular .original-raw' );
|
||||||
|
} else {
|
||||||
|
$original = $gp.editor.current.find( '.source-string__plural .original-raw' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $original.length ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var originalText = $original.text();
|
||||||
|
if ( ! originalText ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$activeTextarea.val( originalText ).focus();
|
||||||
|
|
||||||
|
// Trigger input event for autosize().
|
||||||
|
var event = new Event( 'input' );
|
||||||
|
$activeTextarea[0].dispatchEvent( event );
|
||||||
|
};
|
||||||
|
$gp.editor.tab = function() {
|
||||||
|
var text_area = $gp.editor.current.find( '.textareas.active textarea' );
|
||||||
|
if ( ! text_area.length ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cursorPos = text_area.prop( 'selectionStart' );
|
||||||
|
var v = text_area.val();
|
||||||
|
var textBefore = v.substring( 0, cursorPos );
|
||||||
|
var textAfter = v.substring( cursorPos, v.length );
|
||||||
|
|
||||||
|
text_area.val( textBefore + '\t' + textAfter );
|
||||||
|
|
||||||
|
text_area.focus();
|
||||||
|
text_area[0].selectionEnd = cursorPos + 1;
|
||||||
|
},
|
||||||
|
$gp.editor.newline = function() {
|
||||||
|
var text_area = $gp.editor.current.find( '.textareas.active textarea' );
|
||||||
|
if ( ! text_area.length ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cursorPos = text_area.prop( 'selectionStart' );
|
||||||
|
var v = text_area.val();
|
||||||
|
var textBefore = v.substring( 0, cursorPos );
|
||||||
|
var textAfter = v.substring( cursorPos, v.length );
|
||||||
|
|
||||||
|
text_area.val( textBefore + '\n' + textAfter );
|
||||||
|
|
||||||
|
text_area.focus();
|
||||||
|
text_area[0].selectionEnd = cursorPos + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
function switchTextDirection() {
|
||||||
|
var direction = $( this ).is( '.translation-actions__ltr') ? 'ltr' : 'rtl';
|
||||||
|
|
||||||
|
var $wrapper = $gp.editor.current.find( '.translation-wrapper' );
|
||||||
|
if ( ! $wrapper.length ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$wrapper.removeClass( 'textarea-direction-rtl textarea-direction-ltr' );
|
||||||
|
$wrapper.addClass( 'textarea-direction-' + direction );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the modal for translation help.
|
||||||
|
function openHelpModal() {
|
||||||
|
var $modal = $( '#wporg-translation-help-modal' );
|
||||||
|
var $closeButton = $modal.find( '.wporg-translate-modal__close' );
|
||||||
|
|
||||||
|
$html.addClass( 'modal-open' );
|
||||||
|
$modal.addClass( 'wporg-translate-modal--open' );
|
||||||
|
$closeButton.focus();
|
||||||
|
|
||||||
|
$document.on( 'keydown.modal', function( event ) {
|
||||||
|
if ( 27 !== event.which ) { // ESC key.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$modal.removeClass( 'wporg-translate-modal--open' );
|
||||||
|
$html.removeClass( 'modal-open' );
|
||||||
|
$document.off( 'keydown.modal' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
$closeButton.one( 'click', function() {
|
||||||
|
$modal.removeClass( 'wporg-translate-modal--open' );
|
||||||
|
$html.removeClass( 'modal-open' );
|
||||||
|
$document.off( 'keydown.modal' );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
$gp.editor.keydown = ( function( original ) {
|
||||||
|
return function( event ) {
|
||||||
|
// Shift-Enter = Save.
|
||||||
|
if ( 13 === event.keyCode && event.shiftKey ) {
|
||||||
|
var $textarea = $( event.target );
|
||||||
|
|
||||||
|
if ( ! $textarea.val().trim() ) {
|
||||||
|
$gp.notices.error( 'Translation is empty.' );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check plural forms.
|
||||||
|
var $textareas = $gp.editor.current.find( '.textareas:not(.active) textarea' );
|
||||||
|
var isValid = true;
|
||||||
|
$textareas.each( function() {
|
||||||
|
if ( ! this.value.trim() ) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
if ( ! isValid ) {
|
||||||
|
$gp.notices.error( 'Translation is empty.' );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$gp.editor.save( $gp.editor.current.find( 'button.translation-actions__save' ) );
|
||||||
|
|
||||||
|
// Ctrl-Enter or Ctrl-Shift-B = Copy original.
|
||||||
|
} else if (
|
||||||
|
( 13 === event.keyCode && event.ctrlKey ) ||
|
||||||
|
( 66 === event.keyCode && event.shiftKey && event.ctrlKey ) )
|
||||||
|
{
|
||||||
|
var $button = $gp.editor.current.find( 'button.translation-actions__copy' );
|
||||||
|
|
||||||
|
$button.trigger( 'click' );
|
||||||
|
} else {
|
||||||
|
return original.apply( $gp.editor, arguments );
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})( $gp.editor.keydown );
|
||||||
|
|
||||||
|
// Store the open/close state of <details> element in locale storage and apply state when editor is shown.
|
||||||
|
var DETAILS_STORE_KEY = 'translate-details-state';
|
||||||
|
function updateDetailsState( type, state ) {
|
||||||
|
if ( ! hasStorage ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var store = window.localStorage.getItem( DETAILS_STORE_KEY );
|
||||||
|
var states = store ? JSON.parse( store ) : {};
|
||||||
|
|
||||||
|
states[ type ] = state;
|
||||||
|
|
||||||
|
window.localStorage.setItem( DETAILS_STORE_KEY, JSON.stringify( states ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDetails( event ) {
|
||||||
|
var $el = $( event.target ).closest( 'details' );
|
||||||
|
var isClosed = $el.attr( 'open' ) === 'open'; // Gets closed when open attribute was previously set.
|
||||||
|
var className = $el.attr( 'class' ).replace( /^(\S*).*/, '$1' );
|
||||||
|
|
||||||
|
updateDetailsState( className, isClosed ? 'close' : 'open' );
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyDetailsState() {
|
||||||
|
if ( ! hasStorage || ! $gp.editor.current.length ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var store = window.localStorage.getItem( DETAILS_STORE_KEY );
|
||||||
|
var states = store ? JSON.parse( store ) : {};
|
||||||
|
|
||||||
|
for ( var type in states ) {
|
||||||
|
var state = states[ type ];
|
||||||
|
|
||||||
|
if ( 'open' === state ) {
|
||||||
|
$gp.editor.current.find( '.' + type ).attr( 'open', 'open' );
|
||||||
|
} else {
|
||||||
|
$gp.editor.current.find( '.' + type ).removeAttr( 'open' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Click on the last tab opened in the previous row, to show the same tab in the current row.
|
||||||
|
$gp.editor.current.find( '.' + states['last-tab-type-open'] ).first().click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeRightTab( event ) {
|
||||||
|
var tab = $( this );
|
||||||
|
var tabType = tab.attr( 'class' ).split(' ')[0];
|
||||||
|
var tabId = tab.attr( 'data-tab' );
|
||||||
|
var divId = tabId.replace( 'tab', 'div' );
|
||||||
|
var originalId = tabId.replace( /[^\d-]/g, '' ).replace( /^-+/g, '' );
|
||||||
|
changeVisibleTab( tab );
|
||||||
|
changeVisibleDiv( divId, originalId );
|
||||||
|
updateDetailsState( 'last-tab-type-open', tabType );
|
||||||
|
// Avoid to execute the code from the gp-translation-helpers plugin.
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides all tabs and show one of them, the last clicked.
|
||||||
|
*
|
||||||
|
* @param {Object} tab The selected tab.
|
||||||
|
*/
|
||||||
|
function changeVisibleTab( tab ) {
|
||||||
|
var tabId = tab.attr( 'data-tab' );
|
||||||
|
tab.siblings().removeClass( 'current' );
|
||||||
|
tab.parents( '.sidebar-tabs ' ).find( '.helper' ).removeClass( 'current' );
|
||||||
|
tab.addClass( 'current' );
|
||||||
|
|
||||||
|
$( '#' + tabId ).addClass( 'current' );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides all divs and show one of them, the last clicked.
|
||||||
|
*
|
||||||
|
* @param {string} tabId The select tab id.
|
||||||
|
* @param {number} originalId The id of the original string to translate.
|
||||||
|
*/
|
||||||
|
function changeVisibleDiv( tabId, originalId ) {
|
||||||
|
$( '#sidebar-div-meta-' + originalId ).hide();
|
||||||
|
$( '#sidebar-div-discussion-' + originalId ).hide();
|
||||||
|
$( '#sidebar-div-others-' + originalId ).hide();
|
||||||
|
$( '#' + tabId ).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
$gp.editor.show = ( function( original ) {
|
||||||
|
return function() {
|
||||||
|
original.apply( $gp.editor, arguments );
|
||||||
|
|
||||||
|
applyDetailsState();
|
||||||
|
}
|
||||||
|
})( $gp.editor.show );
|
||||||
|
|
||||||
|
$gp.editor.install_hooks = ( function( original ) {
|
||||||
|
return function() {
|
||||||
|
original.apply( $gp.editor, arguments );
|
||||||
|
|
||||||
|
$( $gp.editor.table )
|
||||||
|
.on( 'click', 'button.translation-form-list__tab', switchPluralTabs )
|
||||||
|
.on( 'click', 'button.panel-header-actions__previous', $gp.editor.prev )
|
||||||
|
.on( 'click', 'button.panel-header-actions__next', $gp.editor.next )
|
||||||
|
.on( 'click', 'button.panel-header-actions__cancel', $gp.editor.hooks.cancel )
|
||||||
|
.on( 'click', 'button.translation-actions__copy', $gp.editor.hooks.copy )
|
||||||
|
.on( 'click', 'button.translation-actions__insert-tab', $gp.editor.hooks.tab )
|
||||||
|
.on( 'click', 'button.translation-actions__save', $gp.editor.hooks.ok )
|
||||||
|
.on( 'click', 'button.translation-actions__help', openHelpModal )
|
||||||
|
.on( 'click', 'button.translation-actions__ltr', switchTextDirection )
|
||||||
|
.on( 'click', 'button.translation-actions__rtl', switchTextDirection )
|
||||||
|
.on( 'focus', 'textarea', textareaAutosize )
|
||||||
|
.on( 'click', 'summary', toggleDetails )
|
||||||
|
.on( 'click', 'button.button-menu__toggle', toggleLinkMenu )
|
||||||
|
.on( 'click', '.sidebar-tabs li', changeRightTab );
|
||||||
|
}
|
||||||
|
})( $gp.editor.install_hooks );
|
||||||
|
|
||||||
|
})( jQuery );
|
486
content/plugins/f-translate/templates/locale-project.php
Normal file
486
content/plugins/f-translate/templates/locale-project.php
Normal file
|
@ -0,0 +1,486 @@
|
||||||
|
<?php
|
||||||
|
gp_title( sprintf( __( 'Projects translated to %s < GlotPress' ), esc_html( $locale->english_name ) ) );
|
||||||
|
|
||||||
|
$breadcrumb = array();
|
||||||
|
$breadcrumb[] = gp_link_get( '/', __( 'Locales' ) );
|
||||||
|
$breadcrumb[] = gp_link_get( gp_url_join( '/locale', $locale_slug, $set_slug ), esc_html( $locale->english_name ) );
|
||||||
|
$breadcrumb[] = gp_link_get( gp_url_join( '/locale', $locale_slug, $set_slug, $project->path ), esc_html( $project->name ) );
|
||||||
|
$breadcrumb[] = $sub_project->name;
|
||||||
|
gp_breadcrumb( $breadcrumb );
|
||||||
|
gp_tmpl_header();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="project-header">
|
||||||
|
<p class="project-description"><?php
|
||||||
|
$description = apply_filters( 'project_description', $sub_project->description, $sub_project );
|
||||||
|
|
||||||
|
// Localize the links to the currently viewed locale.
|
||||||
|
$description = WordPressdotorg\GlotPress\Customizations\Plugin::get_instance()->localize_links( $description, $locale->wp_locale );
|
||||||
|
|
||||||
|
echo $description;
|
||||||
|
?></p>
|
||||||
|
|
||||||
|
<div class="project-box percent-<?php echo $project_status->percent_complete; ?>">
|
||||||
|
<div class="project-box-header">
|
||||||
|
<div class="project-icon">
|
||||||
|
<?php echo $project_icon; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="project-meta">
|
||||||
|
<li class="project-name"><?php echo $sub_project->name; ?></li>
|
||||||
|
<li class="locale-english"><?php echo $locale->english_name; ?></li>
|
||||||
|
<?php if ( $locale->english_name !== $locale->native_name ) : ?>
|
||||||
|
<li class="locale-native"><?php echo $locale->native_name; ?></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
<li class="locale-code">
|
||||||
|
<?php
|
||||||
|
echo $locale->wp_locale;
|
||||||
|
|
||||||
|
if ( count( $variants ) > 1 ) {
|
||||||
|
?>
|
||||||
|
<select id="variant-selector" name="variant">
|
||||||
|
<?php
|
||||||
|
foreach ( $variants as $variant ) {
|
||||||
|
printf(
|
||||||
|
'<option name="%s" data-project-url="%s"%s>%s</option>',
|
||||||
|
$variant,
|
||||||
|
esc_url( gp_url_join( '/locale', $locale_slug, $variant, $sub_project->path ) ),
|
||||||
|
( $set_slug == $variant ) ? ' selected="selected"' : '',
|
||||||
|
ucfirst( $variant )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</li>
|
||||||
|
<?php if ( $locale_glossary ) : ?>
|
||||||
|
<li class="locale-glossary">
|
||||||
|
<a href="<?php echo esc_url( gp_url_join( gp_url( '/locale' ), $locale_slug, $set_slug, 'glossary' ) ); ?>" class="glossary-link"><?php _e( 'Locale Glossary', 'glotpress' ); ?></a>
|
||||||
|
</li>
|
||||||
|
<?php elseif ( $can_create_locale_glossary ) : ?>
|
||||||
|
<li class="locale-glossary">
|
||||||
|
<a href="<?php echo esc_url( gp_url_join( gp_url( '/locale' ), $locale_slug, $set_slug, 'glossary' ) ); ?>" class="glossary-link"><?php _e( 'Create Locale Glossary', 'glotpress' ); ?></a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="project-status">
|
||||||
|
<?php echo $project_status->percent_complete . '%'; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-status-progress percent">
|
||||||
|
<div class="percent-complete" style="width:<?php echo $project_status->percent_complete; ?>%;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-box-footer">
|
||||||
|
<ul class="projects-dropdown">
|
||||||
|
<li><span>All Sub-Projects</span>
|
||||||
|
<ul>
|
||||||
|
<?php
|
||||||
|
// Show the current project if it has strings.
|
||||||
|
if ( $sub_project_status->all_count ) {
|
||||||
|
printf(
|
||||||
|
'<li><a href="%s">%s <span>%s</span></a>',
|
||||||
|
esc_url( gp_url_project( $sub_project->path, gp_url_join( $locale->slug, $set_slug ) ) ),
|
||||||
|
$sub_project->name,
|
||||||
|
$sub_project_status->percent_complete . '%'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $sub_projects as $_sub_project ) {
|
||||||
|
$status = $sub_project_statuses[ $_sub_project->slug ];
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<li><a href="%s">%s <span>%s</span></a>',
|
||||||
|
esc_url( gp_url_project( $_sub_project->path, gp_url_join( $locale->slug, $set_slug ) ) ),
|
||||||
|
$_sub_project->name,
|
||||||
|
$status->percent_complete . '%'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ( ! $sub_project->active ) : ?>
|
||||||
|
<div class="wporg-notice wporg-notice-warning">
|
||||||
|
<p>This project is no longer actively used. Translations remain for archiving purposes.</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ( 'wp-plugins' === $project->path ) {
|
||||||
|
if ( ! in_array( 'dev', $sub_project_slugs ) && ! in_array( 'stable', $sub_project_slugs ) ) {
|
||||||
|
?>
|
||||||
|
<div class="wporg-notice wporg-notice-error">
|
||||||
|
<p>This plugin is not <a href="https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/">properly prepared for localization</a>. If you would like to translate this plugin, <a href="<?php echo esc_url( 'https://wordpress.org/support/plugin/' . $sub_project->slug ); ?>">please contact the author</a>.</p>
|
||||||
|
<p>Import results are logged on Slack in the <a href="https://wordpress.slack.com/archives/C0E7F4RND" >#meta-language-packs</a> channel. Please see the <a href="https://make.wordpress.org/meta/handbook/documentation/translations/#how-to-handle-this-plugin-is-not-properly-prepared-for-localization-warning">handbook for more information about Slack and possible errors</a>.</p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
} else {
|
||||||
|
$stable_project_slug = in_array( 'stable', $sub_project_slugs, true ) ? 'stable' : 'dev';
|
||||||
|
$stable_project_name = 'stable' === $stable_project_slug ? 'Stable (latest release)' : 'Development (trunk)';
|
||||||
|
$status = $sub_project_statuses[ $stable_project_slug ];
|
||||||
|
?>
|
||||||
|
<div class="wporg-notice wporg-notice-info">
|
||||||
|
<p>Translations for the readme are published almost immediately.
|
||||||
|
The initial language pack for the plugin will be generated when 90% of the <a href="<?php echo esc_url( gp_url_project( $sub_project->path, gp_url_join( $stable_project_slug, $locale->slug, $set_slug ) ) ); ?>"><?php echo $stable_project_name; ?></a> sub-project strings have been translated (currently <?php echo $status->percent_complete . '%'; ?>).</p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
} elseif ( 'wp-themes' === $project->path ) {
|
||||||
|
?>
|
||||||
|
<div class="wporg-notice wporg-notice-info">
|
||||||
|
<p>The initial language pack for the theme will be generated when 90% of the project strings have been translated (currently <?php echo $sub_project_status->percent_complete . '%'; ?>).</p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="locale-project">
|
||||||
|
<table class="gp-table locale-sub-projects">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="header"><?php _e( 'Set / Sub Project' ); ?></th>
|
||||||
|
<th><?php _e( 'Translated' ); ?></th>
|
||||||
|
<th><?php _e( 'Fuzzy' ); ?></th>
|
||||||
|
<th><?php _e( 'Untranslated' ); ?></th>
|
||||||
|
<th><?php _e( 'Waiting' ); ?></th>
|
||||||
|
<th><?php _e( 'Changes requested' ); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
// Show the current project if it has strings.
|
||||||
|
if ( $sub_project_status->all_count ) {
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td class="set-name">
|
||||||
|
<strong><?php gp_link( gp_url_project( $sub_project->path, gp_url_join( $locale->slug, $set_slug ) ), $sub_project->name ); ?></strong>
|
||||||
|
<?php if ( $sub_project_status->percent_complete > 90 ) : ?>
|
||||||
|
<span class="sub-project-status percent-90"><?php echo $sub_project_status->percent_complete; ?>%</span>
|
||||||
|
<?php else : ?>
|
||||||
|
<span class="sub-project-status"><?php echo $sub_project_status->percent_complete; ?>%</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="stats translated">
|
||||||
|
<?php gp_link( gp_url_project( $sub_project->path, gp_url_join( $locale->slug, $set_slug ), array( 'filters[translated]' => 'yes', 'filters[status]' => 'current') ), number_format_i18n( $sub_project_status->current_count ) ); ?>
|
||||||
|
</td>
|
||||||
|
<td class="stats fuzzy">
|
||||||
|
<?php gp_link( gp_url_project( $sub_project->path, gp_url_join( $locale->slug, $set_slug ), array( 'filters[translated]' => 'yes', 'filters[status]' => 'fuzzy' ) ), number_format_i18n( $sub_project_status->fuzzy_count ) ); ?>
|
||||||
|
</td>
|
||||||
|
<td class="stats untranslated">
|
||||||
|
<?php gp_link( gp_url_project( $sub_project->path, gp_url_join( $locale->slug, $set_slug ), array( 'filters[status]' => 'untranslated' ) ), number_format_i18n( $sub_project_status->untranslated_count ) ); ?>
|
||||||
|
</td>
|
||||||
|
<td class="stats waiting">
|
||||||
|
<?php gp_link( gp_url_project( $sub_project->path, gp_url_join( $locale->slug, $set_slug ), array( 'filters[translated]' => 'yes', 'filters[status]' => 'waiting' ) ), number_format_i18n( $sub_project_status->waiting_count ) ); ?>
|
||||||
|
</td>
|
||||||
|
<td class="stats changesrequested">
|
||||||
|
<?php gp_link( gp_url_project( $sub_project->path, gp_url_join( $locale->slug, $set_slug ), array( 'filters[changesrequested]' => 'yes', 'filters[status]' => 'changesrequested' ) ), number_format_i18n( $sub_project_status->changesrequested_count ) ); ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ( 'wp-plugins' === $project->path ) {
|
||||||
|
// Ensure consistent order of development and stable projects.
|
||||||
|
usort( $sub_projects, function( $a, $b ) {
|
||||||
|
$a_is_dev = ( substr( $a->slug, 0, 3 ) == 'dev' );
|
||||||
|
$b_is_dev = ( substr( $b->slug, 0, 3 ) == 'dev' );
|
||||||
|
|
||||||
|
// Sort same-type projects alphabetically
|
||||||
|
if ( $a_is_dev === $b_is_dev ) {
|
||||||
|
return strnatcasecmp( $a->name, $b->name );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort Stable before Dev.
|
||||||
|
return $a_is_dev <=> $b_is_dev;
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $sub_projects as $sub_project ) {
|
||||||
|
$status = $sub_project_statuses[ $sub_project->slug ];
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td class="set-name">
|
||||||
|
<strong><?php gp_link( gp_url_project( $sub_project->path, gp_url_join( $locale->slug, $set_slug ) ), $sub_project->name ); ?></strong>
|
||||||
|
<?php if ( $status->percent_complete > 90 ) : ?>
|
||||||
|
<span class="sub-project-status percent-90"><?php echo $status->percent_complete; ?>%</span>
|
||||||
|
<?php else : ?>
|
||||||
|
<span class="sub-project-status"><?php echo $status->percent_complete; ?>%</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="stats translated">
|
||||||
|
<?php gp_link( gp_url_project( $sub_project->path, gp_url_join( $locale->slug, $set_slug ), array( 'filters[translated]' => 'yes', 'filters[status]' => 'current') ), number_format_i18n( $status->current_count ) ); ?>
|
||||||
|
</td>
|
||||||
|
<td class="stats fuzzy">
|
||||||
|
<?php gp_link( gp_url_project( $sub_project->path, gp_url_join( $locale->slug, $set_slug ), array( 'filters[translated]' => 'yes', 'filters[status]' => 'fuzzy' ) ), number_format_i18n( $status->fuzzy_count ) ); ?>
|
||||||
|
</td>
|
||||||
|
<td class="stats untranslated">
|
||||||
|
<?php gp_link( gp_url_project( $sub_project->path, gp_url_join( $locale->slug, $set_slug ), array( 'filters[status]' => 'untranslated' ) ), number_format_i18n( $status->untranslated_count ) ); ?>
|
||||||
|
</td>
|
||||||
|
<td class="stats waiting">
|
||||||
|
<?php gp_link( gp_url_project( $sub_project->path, gp_url_join( $locale->slug, $set_slug ), array( 'filters[translated]' => 'yes', 'filters[status]' => 'waiting' ) ), number_format_i18n( $status->waiting_count ) ); ?>
|
||||||
|
</td>
|
||||||
|
<td class="stats changesrequested">
|
||||||
|
<?php gp_link( gp_url_project( $sub_project->path, gp_url_join( $locale->slug, $set_slug ), array( 'filters[changesrequested]' => 'yes', 'filters[status]' => 'changesrequested' ) ), number_format_i18n( $status->changesrequested_count ) ); ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="locale-project-contributors">
|
||||||
|
<div class="locale-project-contributors-group locale-project-contributors-contributors">
|
||||||
|
<h3>Translation Contributors</h3>
|
||||||
|
<?php if ( $locale_contributors['contributors'] ) : ?>
|
||||||
|
<table class="gp-table locale-project-contributors-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="contributor-details">Contributor</th>
|
||||||
|
<th class="contributor-stats">Translations</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
foreach ( $locale_contributors['contributors'] as $contributor ) {
|
||||||
|
$detailed = '';
|
||||||
|
|
||||||
|
if ( 'wp-plugins' === $project->path ) {
|
||||||
|
// Ensure consistent order of development and stable projects.
|
||||||
|
usort( $contributor->detailed, function( $a, $b ) {
|
||||||
|
return strnatcasecmp( $a->project->name, $b->project->name );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $contributor->detailed as $detail_project_id => $detail_data ) {
|
||||||
|
$detailed .= '<strong class="detailed__project-name">' . $detail_data->project->name . ':</strong>';
|
||||||
|
|
||||||
|
if ( $detail_data->total_count > 0 ) {
|
||||||
|
$total_count = gp_link_get(
|
||||||
|
gp_url_project(
|
||||||
|
$detail_data->project->path,
|
||||||
|
gp_url_join( $locale->slug, $set_slug ),
|
||||||
|
[
|
||||||
|
'filters[translated]' => 'yes',
|
||||||
|
'filters[status]' => 'current_or_waiting_or_fuzzy_or_untranslated',
|
||||||
|
'filters[user_login]' => $contributor->login,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
number_format_i18n( $detail_data->total_count )
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$total_count = number_format_i18n( $detail_data->total_count );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $detail_data->current_count > 0 ) {
|
||||||
|
$current_count = gp_link_get(
|
||||||
|
gp_url_project(
|
||||||
|
$detail_data->project->path,
|
||||||
|
gp_url_join( $locale->slug, $set_slug ),
|
||||||
|
[
|
||||||
|
'filters[translated]' => 'yes',
|
||||||
|
'filters[status]' => 'current',
|
||||||
|
'filters[user_login]' => $contributor->login,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
number_format_i18n( $detail_data->current_count )
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$current_count = number_format_i18n( $detail_data->current_count );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $detail_data->waiting_count > 0 ) {
|
||||||
|
$waiting_count = gp_link_get(
|
||||||
|
gp_url_project(
|
||||||
|
$detail_data->project->path,
|
||||||
|
gp_url_join( $locale->slug, $set_slug ),
|
||||||
|
[
|
||||||
|
'filters[translated]' => 'yes',
|
||||||
|
'filters[status]' => 'waiting',
|
||||||
|
'filters[user_login]' => $contributor->login,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
number_format_i18n( $detail_data->waiting_count )
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$waiting_count = number_format_i18n( $detail_data->waiting_count );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $detail_data->fuzzy_count > 0 ) {
|
||||||
|
$fuzzy_count = gp_link_get(
|
||||||
|
gp_url_project(
|
||||||
|
$detail_data->project->path,
|
||||||
|
gp_url_join( $locale->slug, $set_slug ),
|
||||||
|
[
|
||||||
|
'filters[translated]' => 'yes',
|
||||||
|
'filters[status]' => 'fuzzy',
|
||||||
|
'filters[user_login]' => $contributor->login,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
number_format_i18n( $detail_data->fuzzy_count )
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$fuzzy_count = number_format_i18n( $detail_data->fuzzy_count );
|
||||||
|
}
|
||||||
|
|
||||||
|
$detailed .= sprintf(
|
||||||
|
'
|
||||||
|
<div class="total">
|
||||||
|
<p>%s</p>
|
||||||
|
</div>
|
||||||
|
<div class="current">
|
||||||
|
<p>%s</p>
|
||||||
|
</div>
|
||||||
|
<div class="waiting">
|
||||||
|
<p>%s</p>
|
||||||
|
</div>
|
||||||
|
<div class="fuzzy">
|
||||||
|
<p>%s</p>
|
||||||
|
</div>
|
||||||
|
',
|
||||||
|
$total_count,
|
||||||
|
$current_count,
|
||||||
|
$waiting_count,
|
||||||
|
$fuzzy_count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<tr id="contributor-%s">
|
||||||
|
<td class="contributor-details">
|
||||||
|
<div class="contributor-avatar">
|
||||||
|
%s
|
||||||
|
%s
|
||||||
|
</div>
|
||||||
|
<div class="contributor-name">
|
||||||
|
<a href="https://profiles.wordpress.org/%s/">%s</a>
|
||||||
|
<span>Latest translation: %s ago</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="contributor-stats">
|
||||||
|
<div class="total">
|
||||||
|
<span>Total</span>
|
||||||
|
<p>%s</p>
|
||||||
|
</div>
|
||||||
|
<div class="current">
|
||||||
|
<span>Translated</span>
|
||||||
|
<p>%s</p>
|
||||||
|
</div>
|
||||||
|
<div class="waiting">
|
||||||
|
<span>Suggested</span>
|
||||||
|
<p>%s</p>
|
||||||
|
</div>
|
||||||
|
<div class="fuzzy">
|
||||||
|
<span>Fuzzy</span>
|
||||||
|
<p>%s</p>
|
||||||
|
</div>
|
||||||
|
<div class="detailed">
|
||||||
|
<details>
|
||||||
|
<summary>Per project</summary>
|
||||||
|
%s
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>',
|
||||||
|
$contributor->nicename,
|
||||||
|
get_avatar( $contributor->email, 40 ),
|
||||||
|
$contributor->is_editor ? '<span class="translation-editor">Editor</span>' : '',
|
||||||
|
$contributor->nicename,
|
||||||
|
$contributor->display_name ?: $contributor->nicename,
|
||||||
|
human_time_diff( strtotime( $contributor->last_update ) ),
|
||||||
|
number_format_i18n( $contributor->total_count ),
|
||||||
|
number_format_i18n( $contributor->current_count ),
|
||||||
|
number_format_i18n( $contributor->waiting_count ),
|
||||||
|
number_format_i18n( $contributor->fuzzy_count ),
|
||||||
|
$detailed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<?php else : ?>
|
||||||
|
<p>None, be the first?</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="locale-project-contributors-group locale-project-contributors-editors">
|
||||||
|
<h3>Translation Editors</h3>
|
||||||
|
<?php
|
||||||
|
if ( $locale_contributors['editors']['project'] ) :
|
||||||
|
?>
|
||||||
|
<p>These users can validate and approve your translations for this specific project.</p>
|
||||||
|
<ul>
|
||||||
|
<?php
|
||||||
|
foreach ( $locale_contributors['editors']['project'] as $editor ) {
|
||||||
|
printf(
|
||||||
|
'<li><a href="https://profiles.wordpress.org/%s/">%s</a> <a href="https://profiles.wordpress.org/%s/">%s</a></li>',
|
||||||
|
$editor->nicename,
|
||||||
|
get_avatar( $editor->email, 40 ),
|
||||||
|
$editor->nicename,
|
||||||
|
$editor->display_name ?: $editor->nicename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</ul>
|
||||||
|
<?php
|
||||||
|
else :
|
||||||
|
?>
|
||||||
|
<p>There are no editors for this specific project, yet. <a href="https://make.wordpress.org/polyglots/handbook/plugin-theme-authors-guide/pte-request/">Become an editor.</a></p>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
|
|
||||||
|
if ( $locale_contributors['editors']['inherited'] ) :
|
||||||
|
?>
|
||||||
|
<hr>
|
||||||
|
<p>The following users can edit translations for either a parent project or all projects.</p>
|
||||||
|
<ul class="compressed">
|
||||||
|
<?php
|
||||||
|
foreach ( $locale_contributors['editors']['inherited'] as $editor ) {
|
||||||
|
printf(
|
||||||
|
'<li><a href="https://profiles.wordpress.org/%s/">%s %s</a></li>',
|
||||||
|
$editor->nicename,
|
||||||
|
get_avatar( $editor->email, 15 ),
|
||||||
|
$editor->display_name ? $editor->display_name : $editor->nicename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</ul>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
jQuery( document ).ready( function( $ ) {
|
||||||
|
$( '#variant-selector' ).on( 'change', function( event ) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var $optionSelected = $( 'option:selected', this ),
|
||||||
|
projectUrl = $optionSelected.data( 'projectUrl' );
|
||||||
|
|
||||||
|
if ( projectUrl.length ) {
|
||||||
|
window.location = projectUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$( '.projects-dropdown > li' ).on( 'click', function() {
|
||||||
|
$( this ).parent( '.projects-dropdown' ).toggleClass( 'open' );
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php gp_tmpl_footer();
|
320
content/plugins/f-translate/templates/locale-projects.php
Normal file
320
content/plugins/f-translate/templates/locale-projects.php
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
<?php
|
||||||
|
gp_title( sprintf( __( 'Projects translated to %s < GlotPress' ), esc_html( $locale->english_name ) ) );
|
||||||
|
|
||||||
|
$breadcrumb = array();
|
||||||
|
$breadcrumb[] = gp_link_get( '/', __( 'Locales' ) );
|
||||||
|
$breadcrumb[] = gp_link_get( gp_url_join( '/locale', $locale_slug, $set_slug ), esc_html( $locale->english_name ) );
|
||||||
|
$breadcrumb[] = esc_html( $project->name );
|
||||||
|
gp_breadcrumb( $breadcrumb );
|
||||||
|
gp_tmpl_header();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="locale-header">
|
||||||
|
<p class="locale-intro">Translate WordPress, core projects, plugins, and themes into your language. Select your project below to get started.</p>
|
||||||
|
|
||||||
|
<div class="locale-box">
|
||||||
|
<ul class="name">
|
||||||
|
<li class="english"><?php echo $locale->english_name; ?></li>
|
||||||
|
<li class="native"><?php echo $locale->native_name; ?></li>
|
||||||
|
<li class="code">
|
||||||
|
<?php
|
||||||
|
echo $locale->wp_locale;
|
||||||
|
|
||||||
|
if ( count( $variants ) > 1 ) {
|
||||||
|
?>
|
||||||
|
<select id="variant-selector" name="variant">
|
||||||
|
<?php
|
||||||
|
foreach ( $variants as $variant ) {
|
||||||
|
$selected =
|
||||||
|
printf(
|
||||||
|
'<option name="%s" data-project-url="%s"%s>%s</option>',
|
||||||
|
$variant,
|
||||||
|
esc_url( gp_url_join( '/locale', $locale_slug, $variant, $project->slug ) ),
|
||||||
|
( $set_slug == $variant ) ? ' selected="selected"' : '',
|
||||||
|
ucfirst( $variant )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</li>
|
||||||
|
<?php if ( $locale_glossary ) : ?>
|
||||||
|
<li class="locale-glossary">
|
||||||
|
<a href="<?php echo esc_url( gp_url_join( gp_url( '/locale' ), $locale_slug, $set_slug, 'glossary' ) ); ?>" class="glossary-link"><?php _e( 'Locale Glossary', 'glotpress' ); ?></a>
|
||||||
|
</li>
|
||||||
|
<?php elseif ( $can_create_locale_glossary ) : ?>
|
||||||
|
<li class="locale-glossary">
|
||||||
|
<a href="<?php echo esc_url( gp_url_join( gp_url( '/locale' ), $locale_slug, $set_slug, 'glossary' ) ); ?>" class="glossary-link"><?php _e( 'Create Locale Glossary', 'glotpress' ); ?></a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
<div class="contributors">
|
||||||
|
<?php
|
||||||
|
$contributors = sprintf(
|
||||||
|
'<span class="dashicons dashicons-admin-users"></span><br />%s',
|
||||||
|
isset( $contributors_count[ $locale->slug ] ) ? $contributors_count[ $locale->slug ] : 0
|
||||||
|
);
|
||||||
|
echo gp_link_get( 'https://make.wordpress.org/polyglots/teams/?locale=' . $locale->wp_locale, $contributors );
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-header">
|
||||||
|
<ul class="filter-header-links">
|
||||||
|
<?php
|
||||||
|
foreach ( $top_level_projects as $top_level_project ) {
|
||||||
|
printf(
|
||||||
|
'<li><a href="%s"%s>%s</a></li>',
|
||||||
|
esc_url( gp_url_join( '/locale', $locale_slug, $set_slug, $top_level_project->slug ) ),
|
||||||
|
( $top_level_project->path == $project_path ) ? ' class="current"' : '',
|
||||||
|
$top_level_project->name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<li class="filter-header-link__sep" aria-hidden="true">|</li>
|
||||||
|
<li class="has-children">
|
||||||
|
<a href="#">Stats</a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_join( '/locale', $locale_slug, $set_slug, 'stats', 'plugins' ) ); ?>">Plugins</a></li>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_join( '/locale', $locale_slug, $set_slug, 'stats', 'themes' ) ); ?>">Themes</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<?php if ( is_user_logged_in() && 'waiting' === $default_project_tab ) : ?>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_join( '/locale', $locale_slug, $set_slug, 'discussions' ) ); ?>">Discussions</a></li>
|
||||||
|
<?php endif ?>
|
||||||
|
</ul>
|
||||||
|
<div class="search-form">
|
||||||
|
<form>
|
||||||
|
<input type="hidden" name="filter" value="<?php echo esc_attr( $filter ?? '' ); ?>">
|
||||||
|
<input type="hidden" name="without-editors" value="<?php echo esc_attr( $without_editors ? '1' : '' ); ?>">
|
||||||
|
<label class="screen-reader-text" for="projects-filter"><?php esc_attr_e( 'Search projects...' ); ?></label>
|
||||||
|
<input placeholder="<?php esc_attr_e( 'Search projects...' ); ?>" type="search" id="projects-filter" name="s" value="<?php echo esc_attr( $search ?? '' ); ?>" class="filter-search">
|
||||||
|
<input type="submit" value="<?php esc_attr_e( 'Search' ); ?>" class="screen-reader-text" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sort-bar">
|
||||||
|
<form id="sort-filter" action="" method="GET">
|
||||||
|
<input type="hidden" name="s" value="<?php echo esc_attr( $search ?? '' ); ?>">
|
||||||
|
<input type="hidden" name="page" value="1">
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$filter_count = 0;
|
||||||
|
|
||||||
|
if ( 'waiting' === $project->slug && is_user_logged_in() ) {
|
||||||
|
$filter_count++;
|
||||||
|
?>
|
||||||
|
<input id="filter-without-editors" type="checkbox" name="without-editors" value="1"<?php checked( $without_editors ); ?>>
|
||||||
|
<label for="filter-without-editors">Limit to projects without editors</label>
|
||||||
|
<span class="filter-sep" aria-hidden="true">|</span>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$filter_count++;
|
||||||
|
?>
|
||||||
|
<label for="filter">Filter:</label>
|
||||||
|
<select id="filter" class="is-small" name="filter">
|
||||||
|
<?php
|
||||||
|
$sorts = array();
|
||||||
|
if ( is_user_logged_in() && in_array( $project->slug, array( 'waiting', 'wp-themes', 'wp-plugins' ) ) ) {
|
||||||
|
$sorts['special'] = 'Untranslated Favorites, Remaining Strings (Most first)';
|
||||||
|
$sorts['favorites'] = 'My Favorites';
|
||||||
|
}
|
||||||
|
$sorts['strings-remaining'] = 'Remaining Strings (Most first)';
|
||||||
|
$sorts['strings-remaining-asc'] = 'Remaining Strings (Least first)';
|
||||||
|
$sorts['strings-waiting-and-fuzzy'] = 'Waiting + Fuzzy (Most first)';
|
||||||
|
$sorts['strings-waiting-and-fuzzy-asc'] = 'Waiting + Fuzzy (Least first)';
|
||||||
|
$sorts['strings-waiting-and-fuzzy-by-modified-date'] = 'Waiting + Fuzzy (Newest first)';
|
||||||
|
$sorts['strings-waiting-and-fuzzy-by-modified-date-asc'] = 'Waiting + Fuzzy (Oldest first)';
|
||||||
|
$sorts['percent-completed'] = 'Percent Completed (Most first)';
|
||||||
|
$sorts['percent-completed-asc'] = 'Percent Completed (Least first)';
|
||||||
|
|
||||||
|
// Completed project filter, except on the 'waiting' project.
|
||||||
|
if ( $project->slug != 'waiting' ) {
|
||||||
|
$sorts['completed-asc'] = '100% Translations';
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $sorts as $value => $text ) {
|
||||||
|
printf( '<option value="%s" %s>%s</option>', esc_attr( $value ), ( $value == $filter ? 'selected="selected"' : '' ), esc_attr( $text ) );
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button type="submit" class="button is-small"><?php echo ( 1 === $filter_count ? 'Apply Filter' : 'Apply Filters' ); ?></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if ( isset( $pages ) && $pages['pages'] > 1 ) {
|
||||||
|
echo '<div class="projects-paging">';
|
||||||
|
echo gp_pagination( $pages['page'], $pages['per_page'], $pages['results'] );
|
||||||
|
echo '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<div id="projects" class="projects">
|
||||||
|
<?php
|
||||||
|
foreach ( $sub_projects as $sub_project ) {
|
||||||
|
$percent_complete = $waiting = $sub_projects_count = $fuzzy = $remaining = 0;
|
||||||
|
if ( isset( $project_status[ $sub_project->id ] ) ) {
|
||||||
|
$status = $project_status[ $sub_project->id ];
|
||||||
|
$percent_complete = $status->percent_complete;
|
||||||
|
$waiting = $status->waiting_count;
|
||||||
|
$fuzzy = $status->fuzzy_count;
|
||||||
|
$remaining = $status->all_count - $status->current_count;
|
||||||
|
$sub_projects_count = $status->sub_projects_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link directly to the Waiting strings if we're in the Waiting view, otherwise link to the project overview
|
||||||
|
if ( 'waiting' == $project->slug ) {
|
||||||
|
// TODO: Since we're matching parent projects, we can't link to them as they have no direct translation sets.
|
||||||
|
//$project_url = gp_url_join( '/projects', $sub_project->path, $locale_slug, $set_slug ) . '?filters[status]=waiting_or_fuzzy';
|
||||||
|
$project_url = gp_url_join( '/locale', $locale_slug, $set_slug, $sub_project->path );
|
||||||
|
|
||||||
|
$project_name = $sub_project->name;
|
||||||
|
$parent_project_id = $sub_project->parent_project_id;
|
||||||
|
while ( $parent_project_id ) {
|
||||||
|
$parent_project = GP::$project->get( $parent_project_id );
|
||||||
|
$parent_project_id = $parent_project->parent_project_id;
|
||||||
|
$project_name = "{$parent_project->name} - {$project_name}";
|
||||||
|
}
|
||||||
|
} elseif ( 'Patterns' == $project->name ) {
|
||||||
|
$prefix = 'https://translate.wordpress.org/projects/patterns/core';
|
||||||
|
if ( 'patterns' == $sub_project->slug ) {
|
||||||
|
// Remove the URL from the filter for the main Patterns project so that it shows all strings.
|
||||||
|
$suffix = '?filters%5Bterm%5D=&filters%5Bterm_scope%5D=scope_any&filters%5Bstatus%5D=current_or_waiting_or_fuzzy_or_untranslated_or_rejected_or_changesrequested_or_old&filters%5Buser_login%5D=&filter=Apply+Filters&sort%5Bby%5D=priority&sort%5Bhow%5D=desc';
|
||||||
|
} else {
|
||||||
|
$suffix = '?filters%5Bterm%5D=https%3A%2F%2Fwordpress.org%2Fpatterns%2Fpattern%2F' . $sub_project->slug . '%2F&filters%5Bterm_scope%5D=scope_any&filters%5Bstatus%5D=current_or_waiting_or_fuzzy_or_untranslated_or_rejected_or_changesrequested_or_old&filters%5Buser_login%5D=&filter=Apply+Filters&sort%5Bby%5D=priority&sort%5Bhow%5D=desc';
|
||||||
|
}
|
||||||
|
$project_url = $prefix . '/' . $locale_slug . '/' . $set_slug . '/' . $suffix;
|
||||||
|
$project_name = $sub_project->name;
|
||||||
|
} else {
|
||||||
|
$project_url = gp_url_join( '/locale', $locale_slug, $set_slug, $sub_project->path );
|
||||||
|
$project_name = $sub_project->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$project_icon = '';
|
||||||
|
if ( isset( $project_icons[ $sub_project->id ] ) ) {
|
||||||
|
$project_icon = $project_icons[ $sub_project->id ];
|
||||||
|
}
|
||||||
|
|
||||||
|
$classes = 'project-' . sanitize_title_with_dashes( str_replace( '/', '-', $project->path ) );
|
||||||
|
$classes .= ' project-' . sanitize_title_with_dashes( str_replace( '/', '-', $sub_project->path ) );
|
||||||
|
$classes .= ' percent-' . $percent_complete;
|
||||||
|
?>
|
||||||
|
<div class="project <?php echo $classes; ?>">
|
||||||
|
<div class="project-top">
|
||||||
|
<div class="project-icon">
|
||||||
|
<?php echo gp_link_get( $project_url, $project_icon ) ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-name">
|
||||||
|
<h4>
|
||||||
|
<?php echo gp_link_get( $project_url, wp_trim_words( $project_name, 10 ) ); ?>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="project-description">
|
||||||
|
<p><?php
|
||||||
|
$description = wp_strip_all_tags( $sub_project->description );
|
||||||
|
$description = str_replace( array( 'WordPress.org Plugin Page', 'WordPress.org Theme Page' ), '', $description );
|
||||||
|
echo wp_trim_words( $description, 15 );
|
||||||
|
?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-status">
|
||||||
|
<div class="project-status-sub-projects">
|
||||||
|
<span class="project-status-title">Projects</span>
|
||||||
|
<span class="project-status-value"><?php echo number_format_i18n( $sub_projects_count ); ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="project-status-waiting">
|
||||||
|
<span class="project-status-title">Waiting/Fuzzy</span>
|
||||||
|
<span class="project-status-value"><?php echo number_format_i18n( $waiting + $fuzzy ); ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="project-status-remaining">
|
||||||
|
<span class="project-status-title">Remaining</span>
|
||||||
|
<span class="project-status-value"><?php echo number_format_i18n( $remaining ); ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="project-status-progress">
|
||||||
|
<span class="project-status-title">Progress</span>
|
||||||
|
<span class="project-status-value"><?php echo number_format_i18n( $percent_complete ); ?>%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="percent">
|
||||||
|
<div class="percent-complete" style="width:<?php echo $percent_complete; ?>%;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-bottom">
|
||||||
|
<?php echo gp_link_get( $project_url, 'Translate Project', [ 'class' => 'button contribute-button' ] ); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
if ( ! $sub_projects ) {
|
||||||
|
if ( 'waiting' === $project->slug ) {
|
||||||
|
echo '<div class="no-projects-found">No projects with strings awaiting approval!</div>';
|
||||||
|
} else {
|
||||||
|
echo '<div class="no-projects-found">No projects found.</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ( isset( $pages ) && $pages['pages'] > 1 ) {
|
||||||
|
echo '<div class="projects-paging">';
|
||||||
|
echo gp_pagination( $pages['page'], $pages['per_page'], $pages['results'] );
|
||||||
|
echo '</div>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
jQuery( document ).ready( function( $ ) {
|
||||||
|
// Don't filter if there's an existing search term, or if we're paginated
|
||||||
|
// Fall back to a full page reload for those cases.
|
||||||
|
var live_filtering_enabled = ( ! $( '#projects-filter' ).val() && ! $( '.paging' ).length );
|
||||||
|
$rows = $( '#projects' ).find( '.project' );
|
||||||
|
$( '#projects-filter' ).on( 'input keyup', function() {
|
||||||
|
if ( ! live_filtering_enabled ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var words = this.value.toLowerCase().split( ' ' );
|
||||||
|
|
||||||
|
if ( '' === this.value.trim() ) {
|
||||||
|
$rows.show();
|
||||||
|
} else {
|
||||||
|
$rows.hide();
|
||||||
|
$rows.filter( function( i, v ) {
|
||||||
|
var $t = $(this).find( '.project-top' );
|
||||||
|
for ( var d = 0; d < words.length; ++d ) {
|
||||||
|
if ( $t.text().toLowerCase().indexOf( words[d] ) != -1 ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$( '#variant-selector' ).on( 'change', function( event ) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var $optionSelected = $( 'option:selected', this ),
|
||||||
|
projectUrl = $optionSelected.data( 'projectUrl' );
|
||||||
|
|
||||||
|
if ( projectUrl.length ) {
|
||||||
|
window.location = projectUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php gp_tmpl_footer();
|
33
content/plugins/f-translate/templates/project-form.php
Normal file
33
content/plugins/f-translate/templates/project-form.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<dl>
|
||||||
|
<dt><label for="project[name]"><?php _e( 'Name', 'glotpress' ); ?></label></dt>
|
||||||
|
<dd><input type="text" name="project[name]" value="<?php echo esc_html( $project->name ); ?>" id="project[name]"></dd>
|
||||||
|
|
||||||
|
<!-- TODO: make slug edit WordPress style -->
|
||||||
|
<dt><label for="project[slug]"><?php _e( 'Slug', 'glotpress' ); ?></label></dt>
|
||||||
|
<dd>
|
||||||
|
<input type="text" name="project[slug]" value="<?php echo esc_html( $project->slug ); ?>" id="project[slug]">
|
||||||
|
<small><?php _e( 'If you leave the slug empty, it will be derived from the name.', 'glotpress' ); ?></small>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt><label for="project[description]"><?php _e( 'Description', 'glotpress' ); ?></label> <span class="ternary"><?php _e( 'can include HTML', 'glotpress' ); ?></span></dt>
|
||||||
|
<dd><textarea name="project[description]" rows="4" cols="40" id="project[description]"><?php echo esc_html( $project->description ); ?></textarea></dd>
|
||||||
|
|
||||||
|
<dt><label for="project[source_url_template]"><?php _e( 'Source file URL', 'glotpress' ); ?></label></dt>
|
||||||
|
<dd>
|
||||||
|
<input type="text" value="<?php echo esc_html( $project->source_url_template ); ?>" name="project[source_url_template]" id="project[source_url_template]" style="width: 30em;" />
|
||||||
|
<span class="ternary"><?php printf(
|
||||||
|
/* translators: 1: %file%, 2: %line%, 3: https://trac.example.org/browser/%file%#L%line% */
|
||||||
|
__( 'Public URL to a source file in the project. You can use %1$s and %2$s. Ex. %3$s', 'glotpress' ),
|
||||||
|
'<code>%file%</code>',
|
||||||
|
'<code>%line%</code>',
|
||||||
|
'<code>https://trac.example.org/browser/%file%#L%line%</code>'
|
||||||
|
); ?></span>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt><label for="project[parent_project_id]"><?php _e( 'Parent Project (ID)', 'glotpress' ); ?></label></dt>
|
||||||
|
<dd><input type="text" name="project[parent_project_id]" value="<?php echo esc_attr( $project->parent_project_id ); ?>" id="project[parent_project_id]">
|
||||||
|
|
||||||
|
<dt><label for="project[active]"><?php _e( 'Active', 'glotpress' ); ?></label> <input type="checkbox" id="project[active]" name="project[active]" <?php gp_checked( $project->active ); ?> /></dt>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<?php echo gp_js_focus_on( 'project[name]' ); ?>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
gp_title( sprintf( __( 'Mass-create Translation Sets < %s < GlotPress', 'glotpress' ), $project->name ) );
|
||||||
|
gp_breadcrumb_project( $project );
|
||||||
|
gp_tmpl_header();
|
||||||
|
?>
|
||||||
|
<h2><?php _e( 'Mass-create Translation Sets', 'glotpress' ); ?></h2>
|
||||||
|
<p><?php _e( 'Here you can mass-create translation sets in this project.
|
||||||
|
The list of translation sets will be mirrored with the sets of a project you choose.
|
||||||
|
Usually this is one of the parent projects.', 'glotpress' ); ?></p>
|
||||||
|
<form action="<?php echo esc_url( gp_url_current() ); ?>" method="post">
|
||||||
|
<dl>
|
||||||
|
<dt><label for="project_id"><?php _e( 'Project to take translation sets from:', 'glotpress' ); ?></label></dt>
|
||||||
|
<dd><input type="text" name="project_id" value="" id="project_id">
|
||||||
|
</dl>
|
||||||
|
<div id="preview"></div>
|
||||||
|
<p><input type="submit" name="submit" value="<?php esc_attr_e( 'Create Translation Sets', 'glotpress' ); ?>" id="submit" /></p>
|
||||||
|
<?php gp_route_nonce_field( 'mass-create-transation-sets_' . $project->id ); ?>
|
||||||
|
</form>
|
||||||
|
<?php gp_tmpl_footer();
|
|
@ -0,0 +1,188 @@
|
||||||
|
<?php
|
||||||
|
$edit_link = gp_link_project_edit_get( $project, __( '(edit)' ) );
|
||||||
|
gp_title( sprintf( __( '%s < GlotPress' ), esc_html( $project->name ) ) );
|
||||||
|
gp_breadcrumb( array(
|
||||||
|
gp_project_links_from_root( $project ),
|
||||||
|
'Editors & Contributors',
|
||||||
|
) );
|
||||||
|
|
||||||
|
|
||||||
|
gp_enqueue_script( 'common' );
|
||||||
|
gp_enqueue_style( 'chartist' );
|
||||||
|
gp_enqueue_script( 'chartist' );
|
||||||
|
|
||||||
|
gp_tmpl_header();
|
||||||
|
?>
|
||||||
|
<div class="project-header">
|
||||||
|
<p class="project-description"><?php echo apply_filters( 'project_description', $project->description, $project ); ?></p>
|
||||||
|
|
||||||
|
<div class="project-box">
|
||||||
|
<div class="project-box-header">
|
||||||
|
<div class="project-icon">
|
||||||
|
<?php echo $project->icon; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="project-meta">
|
||||||
|
<li class="project-name"><?php echo $project->name; ?> <?php echo $edit_link; ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-box-footer">
|
||||||
|
<ul class="projects-dropdown">
|
||||||
|
<li><span>Contributors</span>
|
||||||
|
<ul>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_project( $project ) ); ?>">Projects</a></li>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_join( gp_url_project( $project ), 'language-packs' ) ); ?>">Language Packs</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-sub-page">
|
||||||
|
|
||||||
|
<h3>Activity</h3>
|
||||||
|
<p>The graph shows the recent activity of your contributors. It’s updated once per day.</p>
|
||||||
|
|
||||||
|
<div class="ct-chart ct-chart-contributors"></div>
|
||||||
|
|
||||||
|
<h3>Teams</h3>
|
||||||
|
<p>For each locale a plugin can have translation editors and contributors. If a locale has no editor yet then you should probably <a href="https://make.wordpress.org/polyglots/handbook/rosetta/theme-plugin-directories/#translating-themes-plugins">make a request</a>.</p>
|
||||||
|
<?php
|
||||||
|
if ( $contributors_by_locale ) {
|
||||||
|
?>
|
||||||
|
<div class="contributors-list-filter">
|
||||||
|
<button type="button" class="button-link filter active" data-filter="all">All</button> |
|
||||||
|
<button type="button" class="button-link filter" data-filter="has-editors">With Editors</button> |
|
||||||
|
<button type="button" class="button-link filter" data-filter="no-editors">Without Editors</button>
|
||||||
|
|
||||||
|
<input type="search" class="search" placeholder="Filter teams…" id="contributors-list-search">
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
echo '<div id="contributors-list" class="contributors-list">';
|
||||||
|
foreach ( $contributors_by_locale as $locale_slug => $data ) {
|
||||||
|
$locale = GP_Locales::by_slug( $locale_slug );
|
||||||
|
$has_editors = ! empty ( $data['editors'] );
|
||||||
|
|
||||||
|
$editors_list = array();
|
||||||
|
foreach ( $data['editors'] as $editor ) {
|
||||||
|
$editors_list[] = sprintf(
|
||||||
|
'<a href="https://profiles.wordpress.org/%s/">%s</a>',
|
||||||
|
$editor->nicename,
|
||||||
|
$editor->display_name ? $editor->display_name : $editor->nicename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $editors_list ) {
|
||||||
|
$editors_list[] = 'None';
|
||||||
|
}
|
||||||
|
|
||||||
|
$contributor_list = array();
|
||||||
|
foreach ( $data['contributors'] as $contributor ) {
|
||||||
|
$contributor_list[] = sprintf(
|
||||||
|
'<a href="https://profiles.wordpress.org/%s/">%s</a>',
|
||||||
|
$contributor->nicename,
|
||||||
|
$contributor->display_name ? $contributor->display_name : $contributor->nicename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $contributor_list ) {
|
||||||
|
$contributor_list[] = 'None';
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<div class="contributors-list-box%s">
|
||||||
|
<h4><span class="locale-name">%s<span> <span class="contributors-count">%s</span> <a href="%s"><span class="locale-code">#%s</span></a></h4>
|
||||||
|
<p><strong>Editors:</strong> %s</p>
|
||||||
|
<p><strong>Contributors:</strong> %s</p>
|
||||||
|
</div>',
|
||||||
|
$has_editors ? ' has-editors' : ' no-editors',
|
||||||
|
$locale->english_name,
|
||||||
|
sprintf( _n( '%s person', '%s persons', $data['count'] ), number_format_i18n( $data['count'] ) ),
|
||||||
|
esc_url( gp_url_join( '/locale', $locale->slug, 'default', $project->path ) ),
|
||||||
|
$locale->wp_locale,
|
||||||
|
wp_sprintf( '%l', $editors_list ),
|
||||||
|
wp_sprintf( '%l', $contributor_list )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo '</div>';
|
||||||
|
} else {
|
||||||
|
echo '<p>The plugin has no contributors, yet.</p>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery( function( $ ) {
|
||||||
|
$( '.projects-dropdown > li' ).on( 'click', function() {
|
||||||
|
$( this ).parent( '.projects-dropdown' ).toggleClass( 'open' );
|
||||||
|
});
|
||||||
|
|
||||||
|
$rows = $( '#contributors-list' ).find( '.contributors-list-box' );
|
||||||
|
$( '#contributors-list-search' ).on( 'input keyup',function() {
|
||||||
|
var words = this.value.toLowerCase().split( ' ' );
|
||||||
|
|
||||||
|
if ( '' === this.value.trim() ) {
|
||||||
|
$rows.show();
|
||||||
|
} else {
|
||||||
|
$rows.hide();
|
||||||
|
$rows.filter( function( i, v ) {
|
||||||
|
var $t = $(this).find( '.locale-name' );
|
||||||
|
for ( var d = 0; d < words.length; ++d ) {
|
||||||
|
if ( $t.text().toLowerCase().indexOf( words[d] ) != -1 ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$( '.contributors-list-filter .filter' ).on( 'click', function() {
|
||||||
|
var $el = $( this ), filter = $el.data( 'filter' );
|
||||||
|
|
||||||
|
$el.siblings( '.active' ).removeClass( 'active' );
|
||||||
|
$el.addClass( 'active' );
|
||||||
|
$( '#contributors-list' ).attr( 'data-current-filter', filter );
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chartist.Line('.ct-chart-contributors', {
|
||||||
|
labels: <?php echo json_encode( $chart_data['labels'] ?? [] ); ?>,
|
||||||
|
series: <?php echo json_encode( $chart_data['series'] ?? [] ); ?>
|
||||||
|
}, {
|
||||||
|
lineSmooth: Chartist.Interpolation.simple({
|
||||||
|
divisor: 2
|
||||||
|
}),
|
||||||
|
low: 0,
|
||||||
|
showPoint: false,
|
||||||
|
showLine: false,
|
||||||
|
showArea: true,
|
||||||
|
fullWidth: true,
|
||||||
|
axisX: {
|
||||||
|
showGrid: false,
|
||||||
|
},
|
||||||
|
axisY: {
|
||||||
|
onlyInteger: true,
|
||||||
|
offset: 30
|
||||||
|
},
|
||||||
|
chartPadding: {
|
||||||
|
right: 0,
|
||||||
|
left: 0
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
Chartist.plugins.legend()
|
||||||
|
]
|
||||||
|
}, [
|
||||||
|
['screen and (max-width: 500px)', {
|
||||||
|
axisX: {
|
||||||
|
labelInterpolationFnc: function( value, index ) {
|
||||||
|
return index % 2 === 0 ? value : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php gp_tmpl_footer();
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
$edit_link = gp_link_project_edit_get( $project, __( '(edit)' ) );
|
||||||
|
gp_title( sprintf( __( '%s < GlotPress' ), esc_html( $project->name ) ) );
|
||||||
|
gp_breadcrumb( array(
|
||||||
|
gp_project_links_from_root( $project ),
|
||||||
|
'Language Packs',
|
||||||
|
) );
|
||||||
|
|
||||||
|
|
||||||
|
gp_enqueue_script( 'common' );
|
||||||
|
|
||||||
|
gp_tmpl_header();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="project-header">
|
||||||
|
<p class="project-description"><?php echo apply_filters( 'project_description', $project->description, $project ); ?></p>
|
||||||
|
|
||||||
|
<div class="project-box">
|
||||||
|
<div class="project-box-header">
|
||||||
|
<div class="project-icon">
|
||||||
|
<?php echo $project->icon; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="project-meta">
|
||||||
|
<li class="project-name"><?php echo $project->name; ?> <?php echo $edit_link; ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-box-footer">
|
||||||
|
<ul class="projects-dropdown">
|
||||||
|
<li><span>Language Packs</span>
|
||||||
|
<ul>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_project( $project ) ); ?>">Projects</a></li>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_join( gp_url_project( $project ), 'contributors' ) ); ?>">Contributors</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-sub-page">
|
||||||
|
<h3>Language Packs</h3>
|
||||||
|
|
||||||
|
<p>Language packs are installed automatically if they are available. Once a locale has reached the threshold for a package build it will be listed here. It also means that you don’t have to include this language in your plugin anymore.</p>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ( isset( $language_packs->translations ) && $language_packs->translations ) {
|
||||||
|
echo '<ul class="language-packs-list">';
|
||||||
|
foreach ( $language_packs->translations as $language_pack ) {
|
||||||
|
printf(
|
||||||
|
'<li><strong>%s <span class="locale-code">(%s)</span>:</strong> Last updated %s for version %s (<a href="%s">zip</a>)</li>',
|
||||||
|
$language_pack->english_name,
|
||||||
|
$language_pack->language,
|
||||||
|
$language_pack->updated,
|
||||||
|
$language_pack->version,
|
||||||
|
$language_pack->package
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo '</ul>';
|
||||||
|
} else {
|
||||||
|
echo '<p>There are no language packs yet.</p>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery( function( $ ) {
|
||||||
|
$( '.projects-dropdown > li' ).on( 'click', function() {
|
||||||
|
$( this ).parent( '.projects-dropdown' ).toggleClass( 'open' );
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php gp_tmpl_footer();
|
148
content/plugins/f-translate/templates/projects-wp-plugins.php
Normal file
148
content/plugins/f-translate/templates/projects-wp-plugins.php
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
<?php
|
||||||
|
$edit_link = gp_link_project_edit_get( $project, __( '(edit)' ) );
|
||||||
|
$table_headings = [
|
||||||
|
'locale' => 'Locale',
|
||||||
|
'dev' => 'Development',
|
||||||
|
'dev-readme' => 'Development Readme',
|
||||||
|
'stable' => 'Stable',
|
||||||
|
'stable-readme' => 'Stable Readme',
|
||||||
|
'waiting' => 'Waiting/Fuzzy',
|
||||||
|
];
|
||||||
|
|
||||||
|
gp_title( sprintf( __( '%s < GlotPress' ), esc_html( $project->name ) ) );
|
||||||
|
gp_breadcrumb_project( $project );
|
||||||
|
|
||||||
|
gp_enqueue_script( 'common' );
|
||||||
|
gp_enqueue_script( 'tablesorter' );
|
||||||
|
|
||||||
|
gp_tmpl_header();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="project-header">
|
||||||
|
<p class="project-description"><?php echo apply_filters( 'project_description', $project->description, $project ); ?></p>
|
||||||
|
|
||||||
|
<div class="project-box">
|
||||||
|
<div class="project-box-header">
|
||||||
|
<div class="project-icon">
|
||||||
|
<?php echo $project->icon; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="project-meta">
|
||||||
|
<li class="project-name"><?php echo $project->name; ?> <?php echo $edit_link; ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-box-footer">
|
||||||
|
<ul class="projects-dropdown">
|
||||||
|
<li><span>Projects</span>
|
||||||
|
<ul>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_join( gp_url_project( $project ), 'contributors' ) ); ?>">Contributors</a></li>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_join( gp_url_project( $project ), 'language-packs' ) ); ?>">Language Packs</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ( ! $project->active ) : ?>
|
||||||
|
<div class="wporg-notice wporg-notice-warning">
|
||||||
|
<p>This plugin is no longer listed in the plugin directory. Translations remain for archiving purposes.</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ( $has_error ) : ?>
|
||||||
|
<div class="wporg-notice wporg-notice-error">
|
||||||
|
<p>This plugin is not <a href="https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/">properly prepared for localization</a>. If you would like to translate this plugin, <a href="<?php echo esc_url( 'https://wordpress.org/support/plugin/' . $project->slug ); ?>">please contact the author</a>.</p>
|
||||||
|
<p>Import results are logged on Slack in the <a href="https://wordpress.slack.com/archives/C0E7F4RND" >#meta-language-packs</a> channel. Please see the <a href="https://make.wordpress.org/meta/handbook/documentation/translations/#how-to-handle-this-plugin-is-not-properly-prepared-for-localization-warning">handbook for details about Slack and possible errors</a>.</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="stats-table">
|
||||||
|
<table id="stats-table" class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<?php foreach ( $table_headings as $key => $heading ) : ?>
|
||||||
|
<th class="col-<?php echo $key; ?>"><?php echo $heading; ?></th>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
foreach ( $translation_locale_complete as $locale_slug => $total_complete ) :
|
||||||
|
$gp_locale = GP_Locales::by_slug( $locale_slug );
|
||||||
|
|
||||||
|
if ( ! $gp_locale || ! $gp_locale->wp_locale ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
list( $locale, $set_slug ) = array_merge( explode( '/', $locale_slug ), [ 'default' ] );
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<th title="<?php echo esc_attr( $gp_locale->wp_locale ); ?>">
|
||||||
|
<a href="<?php echo esc_url( gp_url( gp_url_join( 'locale', $locale, $set_slug, $project->path ) ) ); ?>">
|
||||||
|
<?php echo esc_html( $gp_locale->english_name ); ?>
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<?php
|
||||||
|
if ( $translation_locale_statuses[ $locale_slug ] ) :
|
||||||
|
foreach ( array( 'dev', 'dev-readme', 'stable', 'stable-readme', 'waiting' ) as $subproject_slug ) :
|
||||||
|
if ( isset( $translation_locale_statuses[ $locale_slug ][ $subproject_slug ] ) ) :
|
||||||
|
$percent = $translation_locale_statuses[ $locale_slug ][ $subproject_slug ];
|
||||||
|
|
||||||
|
if ( 'waiting' === $subproject_slug ) :
|
||||||
|
// Color code it on -0~500 waiting strings
|
||||||
|
$percent_class = 100 - min( (int) ( $percent / 50 ) * 10, 100 );
|
||||||
|
|
||||||
|
// It's only 100 if it has 0 strings.
|
||||||
|
if ( 100 == $percent_class && $percent ) {
|
||||||
|
$percent_class = 90;
|
||||||
|
}
|
||||||
|
|
||||||
|
$link_url = gp_url( gp_url_join( 'locale', $locale, $set_slug, $project->path ) );
|
||||||
|
$link_text = number_format( $percent );
|
||||||
|
else :
|
||||||
|
$percent_class = (int) ( $percent / 10 ) * 10;
|
||||||
|
$link_url = gp_url_project( $project->path, gp_url_join( $subproject_slug, $locale, $set_slug ) );
|
||||||
|
$link_text = "$percent%";
|
||||||
|
|
||||||
|
endif;
|
||||||
|
|
||||||
|
echo '<td data-column-title="' . esc_attr( $table_headings[ $subproject_slug ] ) . '" data-sort-value="' . esc_attr( $percent ) . '" class="percent' . $percent_class .'">'. gp_link_get( $link_url, $link_text ) . '</td>';
|
||||||
|
else :
|
||||||
|
echo '<td class="none" data-column-title="" data-sort-value="-1">—</td>';
|
||||||
|
endif;
|
||||||
|
endforeach;
|
||||||
|
else :
|
||||||
|
echo '<td class="none" data-sort-value="-1">—</td>';
|
||||||
|
echo '<td class="none" data-sort-value="-1">—</td>';
|
||||||
|
echo '<td class="none" data-sort-value="-1">—</td>';
|
||||||
|
echo '<td class="none" data-sort-value="-1">—</td>';
|
||||||
|
echo '<td class="none" data-sort-value="-1">—</td>';
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery( function( $ ) {
|
||||||
|
$( '#stats-table' ).tablesorter( {
|
||||||
|
theme: 'wporg-translate',
|
||||||
|
textExtraction: function( node ) {
|
||||||
|
var cellValue = $( node ).text(),
|
||||||
|
sortValue = $( node ).data( 'sortValue' );
|
||||||
|
|
||||||
|
return ( undefined !== sortValue ) ? sortValue : cellValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$( '.projects-dropdown > li' ).on( 'click', function() {
|
||||||
|
$( this ).parent( '.projects-dropdown' ).toggleClass( 'open' );
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php gp_tmpl_footer();
|
|
@ -0,0 +1,187 @@
|
||||||
|
<?php
|
||||||
|
$edit_link = gp_link_project_edit_get( $project, __( '(edit)' ) );
|
||||||
|
gp_title( sprintf( __( '%s < GlotPress' ), esc_html( $project->name ) ) );
|
||||||
|
gp_breadcrumb( array(
|
||||||
|
gp_project_links_from_root( $project ),
|
||||||
|
'Editors & Contributors',
|
||||||
|
) );
|
||||||
|
|
||||||
|
|
||||||
|
gp_enqueue_script( 'common' );
|
||||||
|
gp_enqueue_style( 'chartist' );
|
||||||
|
gp_enqueue_script( 'chartist' );
|
||||||
|
|
||||||
|
gp_tmpl_header();
|
||||||
|
?>
|
||||||
|
<div class="project-header">
|
||||||
|
<p class="project-description"><?php echo apply_filters( 'project_description', $project->description, $project ); ?></p>
|
||||||
|
|
||||||
|
<div class="project-box">
|
||||||
|
<div class="project-box-header">
|
||||||
|
<div class="project-icon">
|
||||||
|
<?php echo $project->icon; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="project-meta">
|
||||||
|
<li class="project-name"><?php echo $project->name; ?> <?php echo $edit_link; ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-box-footer">
|
||||||
|
<ul class="projects-dropdown">
|
||||||
|
<li><span>Contributors</span>
|
||||||
|
<ul>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_project( $project ) ); ?>">Projects</a></li>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_join( gp_url_project( $project ), 'language-packs' ) ); ?>">Language Packs</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-sub-page">
|
||||||
|
|
||||||
|
<h3>Activity</h3>
|
||||||
|
<p>The graph shows the recent activity of your contributors. It’s updated once per day.</p>
|
||||||
|
|
||||||
|
<div class="ct-chart ct-chart-contributors"></div>
|
||||||
|
|
||||||
|
<h3>Teams</h3>
|
||||||
|
<p>For each locale a theme can have translation editors and contributors. If a locale has no editor yet then you should probably <a href="https://make.wordpress.org/polyglots/handbook/rosetta/theme-plugin-directories/#translating-themes-plugins">make a request</a>.</p>
|
||||||
|
<?php
|
||||||
|
if ( $contributors_by_locale ) {
|
||||||
|
?>
|
||||||
|
<div class="contributors-list-filter">
|
||||||
|
<button type="button" class="button-link filter active" data-filter="all">All</button> |
|
||||||
|
<button type="button" class="button-link filter" data-filter="has-editors">With Editors</button> |
|
||||||
|
<button type="button" class="button-link filter" data-filter="no-editors">Without Editors</button>
|
||||||
|
|
||||||
|
<input type="search" class="search" placeholder="Filter teams…" id="contributors-list-search">
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
echo '<div id="contributors-list" class="contributors-list">';
|
||||||
|
foreach ( $contributors_by_locale as $locale_slug => $data ) {
|
||||||
|
$locale = GP_Locales::by_slug( $locale_slug );
|
||||||
|
$has_editors = ! empty ( $data['editors'] );
|
||||||
|
|
||||||
|
$editors_list = array();
|
||||||
|
foreach ( $data['editors'] as $editor ) {
|
||||||
|
$editors_list[] = sprintf(
|
||||||
|
'<a href="https://profiles.wordpress.org/%s/">%s</a>',
|
||||||
|
$editor->nicename,
|
||||||
|
$editor->display_name ? $editor->display_name : $editor->nicename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $editors_list ) {
|
||||||
|
$editors_list[] = 'None';
|
||||||
|
}
|
||||||
|
|
||||||
|
$contributor_list = array();
|
||||||
|
foreach ( $data['contributors'] as $contributor ) {
|
||||||
|
$contributor_list[] = sprintf(
|
||||||
|
'<a href="https://profiles.wordpress.org/%s/">%s</a>',
|
||||||
|
$contributor->nicename,
|
||||||
|
$contributor->display_name ? $contributor->display_name : $contributor->nicename
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $contributor_list ) {
|
||||||
|
$contributor_list[] = 'None';
|
||||||
|
}
|
||||||
|
|
||||||
|
printf(
|
||||||
|
'<div class="contributors-list-box%s">
|
||||||
|
<h4><span class="locale-name">%s<span> <span class="contributors-count">%s</span> <span class="locale-code">#%s</span></h4>
|
||||||
|
<p><strong>Editors:</strong> %s</p>
|
||||||
|
<p><strong>Contributors:</strong> %s</p>
|
||||||
|
</div>',
|
||||||
|
$has_editors ? ' has-editors' : ' no-editors',
|
||||||
|
$locale->english_name,
|
||||||
|
sprintf( _n( '%s person', '%s persons', $data['count'] ), number_format_i18n( $data['count'] ) ),
|
||||||
|
$locale->wp_locale,
|
||||||
|
wp_sprintf( '%l', $editors_list ),
|
||||||
|
wp_sprintf( '%l', $contributor_list )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo '</div>';
|
||||||
|
} else {
|
||||||
|
echo '<p>The plugin has no contributors, yet.</p>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery( function( $ ) {
|
||||||
|
$( '.projects-dropdown > li' ).on( 'click', function() {
|
||||||
|
$( this ).parent( '.projects-dropdown' ).toggleClass( 'open' );
|
||||||
|
});
|
||||||
|
|
||||||
|
$rows = $( '#contributors-list' ).find( '.contributors-list-box' );
|
||||||
|
$( '#contributors-list-search' ).on( 'input keyup',function() {
|
||||||
|
var words = this.value.toLowerCase().split( ' ' );
|
||||||
|
|
||||||
|
if ( '' === this.value.trim() ) {
|
||||||
|
$rows.show();
|
||||||
|
} else {
|
||||||
|
$rows.hide();
|
||||||
|
$rows.filter( function( i, v ) {
|
||||||
|
var $t = $(this).find( '.locale-name' );
|
||||||
|
for ( var d = 0; d < words.length; ++d ) {
|
||||||
|
if ( $t.text().toLowerCase().indexOf( words[d] ) != -1 ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$( '.contributors-list-filter .filter' ).on( 'click', function() {
|
||||||
|
var $el = $( this ), filter = $el.data( 'filter' );
|
||||||
|
|
||||||
|
$el.siblings( '.active' ).removeClass( 'active' );
|
||||||
|
$el.addClass( 'active' );
|
||||||
|
$( '#contributors-list' ).attr( 'data-current-filter', filter );
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chartist.Line('.ct-chart-contributors', {
|
||||||
|
labels: <?php echo json_encode( $chart_data['labels'] ); ?>,
|
||||||
|
series: <?php echo json_encode( $chart_data['series'] ); ?>
|
||||||
|
}, {
|
||||||
|
lineSmooth: Chartist.Interpolation.simple({
|
||||||
|
divisor: 2
|
||||||
|
}),
|
||||||
|
low: 0,
|
||||||
|
showPoint: false,
|
||||||
|
showLine: false,
|
||||||
|
showArea: true,
|
||||||
|
fullWidth: true,
|
||||||
|
axisX: {
|
||||||
|
showGrid: false,
|
||||||
|
},
|
||||||
|
axisY: {
|
||||||
|
onlyInteger: true,
|
||||||
|
offset: 30
|
||||||
|
},
|
||||||
|
chartPadding: {
|
||||||
|
right: 0,
|
||||||
|
left: 0
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
Chartist.plugins.legend()
|
||||||
|
]
|
||||||
|
}, [
|
||||||
|
['screen and (max-width: 500px)', {
|
||||||
|
axisX: {
|
||||||
|
labelInterpolationFnc: function( value, index ) {
|
||||||
|
return index % 2 === 0 ? value : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php gp_tmpl_footer();
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
$edit_link = gp_link_project_edit_get( $project, __( '(edit)' ) );
|
||||||
|
gp_title( sprintf( __( '%s < GlotPress' ), esc_html( $project->name ) ) );
|
||||||
|
gp_breadcrumb( array(
|
||||||
|
gp_project_links_from_root( $project ),
|
||||||
|
'Language Packs',
|
||||||
|
) );
|
||||||
|
|
||||||
|
|
||||||
|
gp_enqueue_script( 'common' );
|
||||||
|
|
||||||
|
gp_tmpl_header();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="project-header">
|
||||||
|
<p class="project-description"><?php echo apply_filters( 'project_description', $project->description, $project ); ?></p>
|
||||||
|
|
||||||
|
<div class="project-box">
|
||||||
|
<div class="project-box-header">
|
||||||
|
<div class="project-icon">
|
||||||
|
<?php echo $project->icon; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="project-meta">
|
||||||
|
<li class="project-name"><?php echo $project->name; ?> <?php echo $edit_link; ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-box-footer">
|
||||||
|
<ul class="projects-dropdown">
|
||||||
|
<li><span>Language Packs</span>
|
||||||
|
<ul>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_project( $project ) ); ?>">Projects</a></li>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_join( gp_url_project( $project ), 'contributors' ) ); ?>">Contributors</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-sub-page">
|
||||||
|
<h3>Language Packs</h3>
|
||||||
|
|
||||||
|
<p>Language packs are installed automatically if they are available. Once a locale has reached the threshold for a package build it will be listed here. It also means that you don’t have to include this language in your theme anymore.</p>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ( isset( $language_packs->translations ) && $language_packs->translations ) {
|
||||||
|
echo '<ul class="language-packs-list">';
|
||||||
|
foreach ( $language_packs->translations as $language_pack ) {
|
||||||
|
printf(
|
||||||
|
'<li><strong>%s <span class="locale-code">(%s)</span>:</strong> Last updated %s for version %s (<a href="%s">zip</a>)</li>',
|
||||||
|
$language_pack->english_name,
|
||||||
|
$language_pack->language,
|
||||||
|
$language_pack->updated,
|
||||||
|
$language_pack->version,
|
||||||
|
$language_pack->package
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo '</ul>';
|
||||||
|
} else {
|
||||||
|
echo '<p>There are no language packs yet.</p>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery( function( $ ) {
|
||||||
|
$( '.projects-dropdown > li' ).on( 'click', function() {
|
||||||
|
$( this ).parent( '.projects-dropdown' ).toggleClass( 'open' );
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php gp_tmpl_footer();
|
138
content/plugins/f-translate/templates/projects-wp-themes.php
Normal file
138
content/plugins/f-translate/templates/projects-wp-themes.php
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
<?php
|
||||||
|
$edit_link = gp_link_project_edit_get( $project, __( '(edit)' ) );
|
||||||
|
$table_headings = [
|
||||||
|
'locale' => 'Locale',
|
||||||
|
'stable' => 'Stable',
|
||||||
|
'waiting' => 'Waiting/Fuzzy',
|
||||||
|
];
|
||||||
|
|
||||||
|
gp_title( sprintf( __( '%s < GlotPress' ), esc_html( $project->name ) ) );
|
||||||
|
gp_breadcrumb_project( $project );
|
||||||
|
|
||||||
|
gp_enqueue_script( 'common' );
|
||||||
|
gp_enqueue_script( 'tablesorter' );
|
||||||
|
|
||||||
|
gp_tmpl_header();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="project-header">
|
||||||
|
<p class="project-description"><?php echo apply_filters( 'project_description', $project->description, $project ); ?></p>
|
||||||
|
|
||||||
|
<div class="project-box">
|
||||||
|
<div class="project-box-header">
|
||||||
|
<div class="project-icon">
|
||||||
|
<?php echo $project->icon; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="project-meta">
|
||||||
|
<li class="project-name"><?php echo $project->name; ?> <?php echo $edit_link; ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-box-footer">
|
||||||
|
<ul class="projects-dropdown">
|
||||||
|
<li><span>Projects</span>
|
||||||
|
<ul>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_join( gp_url_project( $project ), 'contributors' ) ); ?>">Contributors</a></li>
|
||||||
|
<li><a href="<?php echo esc_url( gp_url_join( gp_url_project( $project ), 'language-packs' ) ); ?>">Language Packs</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ( ! $project->active ) : ?>
|
||||||
|
<div class="wporg-notice wporg-notice-warning">
|
||||||
|
<p>This theme is no longer listed in the theme directory. Translations remain for archiving purposes.</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="stats-table">
|
||||||
|
<table id="stats-table" class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<?php foreach ( $table_headings as $key => $heading ) : ?>
|
||||||
|
<th class="col-<?php echo $key; ?>"><?php echo $heading; ?></th>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
foreach ( $translation_locale_complete as $locale_slug => $total_complete ) :
|
||||||
|
$gp_locale = GP_Locales::by_slug( $locale_slug );
|
||||||
|
|
||||||
|
if ( ! $gp_locale || ! $gp_locale->wp_locale ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
list( $locale, $set_slug ) = array_merge( explode( '/', $locale_slug ), [ 'default' ] );
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<th title="<?php echo esc_attr( $gp_locale->wp_locale ); ?>">
|
||||||
|
<a href="<?php echo esc_url( gp_url( gp_url_join( 'locale', $locale, $set_slug, $project->path ) ) ); ?>">
|
||||||
|
<?php echo esc_html( $gp_locale->english_name ); ?>
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<?php
|
||||||
|
if ( $translation_locale_statuses[ $locale_slug ] ) :
|
||||||
|
foreach ( array( 'stable', 'waiting' ) as $subproject_slug ) :
|
||||||
|
if ( isset( $translation_locale_statuses[ $locale_slug ][ $subproject_slug ] ) ) :
|
||||||
|
$percent = $translation_locale_statuses[ $locale_slug ][ $subproject_slug ];
|
||||||
|
|
||||||
|
if ( 'waiting' === $subproject_slug ) :
|
||||||
|
// Color code it on -0~500 waiting strings
|
||||||
|
$percent_class = 100 - min( (int) ( $percent / 50 ) * 10, 100 );
|
||||||
|
|
||||||
|
// It's only 100 if it has 0 strings.
|
||||||
|
if ( 100 == $percent_class && $percent ) {
|
||||||
|
$percent_class = 90;
|
||||||
|
}
|
||||||
|
|
||||||
|
$link_url = gp_url( gp_url_join( 'locale', $locale, $set_slug, $project->path ) );
|
||||||
|
$link_text = number_format( $percent );
|
||||||
|
else :
|
||||||
|
$percent_class = (int) ( $percent / 10 ) * 10;
|
||||||
|
$link_url = gp_url_project( $project->path, gp_url_join( $locale, $set_slug ) );
|
||||||
|
$link_text = "$percent%";
|
||||||
|
|
||||||
|
endif;
|
||||||
|
|
||||||
|
echo '<td data-column-title="' . esc_attr( $table_headings[ $subproject_slug ] ) . '" data-sort-value="' . esc_attr( $percent ) . '" class="percent' . $percent_class .'">'. gp_link_get( $link_url, $link_text ) . '</td>';
|
||||||
|
else :
|
||||||
|
echo '<td class="none" data-column-title="" data-sort-value="-1">—</td>';
|
||||||
|
endif;
|
||||||
|
endforeach;
|
||||||
|
else :
|
||||||
|
echo '<td class="none" data-sort-value="-1">—</td>';
|
||||||
|
echo '<td class="none" data-sort-value="-1">—</td>';
|
||||||
|
echo '<td class="none" data-sort-value="-1">—</td>';
|
||||||
|
echo '<td class="none" data-sort-value="-1">—</td>';
|
||||||
|
echo '<td class="none" data-sort-value="-1">—</td>';
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery( function( $ ) {
|
||||||
|
$( '#stats-table' ).tablesorter( {
|
||||||
|
theme: 'wporg-translate',
|
||||||
|
textExtraction: function( node ) {
|
||||||
|
var cellValue = $( node ).text(),
|
||||||
|
sortValue = $( node ).data( 'sortValue' );
|
||||||
|
|
||||||
|
return ( undefined !== sortValue ) ? sortValue : cellValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$( '.projects-dropdown > li' ).on( 'click', function() {
|
||||||
|
$( this ).parent( '.projects-dropdown' ).toggleClass( 'open' );
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php gp_tmpl_footer();
|
259
content/plugins/f-translate/templates/settings-edit.php
Normal file
259
content/plugins/f-translate/templates/settings-edit.php
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* The user settings block
|
||||||
|
*
|
||||||
|
* A single table that contains all of the user settings, which is included as part of gp-templates/settings.php.
|
||||||
|
*
|
||||||
|
* @link http://glotpress.org
|
||||||
|
*
|
||||||
|
* @package GlotPress
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
$gp_per_page = (int) get_user_option( 'gp_per_page' );
|
||||||
|
if ( 0 === $gp_per_page ) {
|
||||||
|
$gp_per_page = 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
$gp_default_sort = get_user_option( 'gp_default_sort' );
|
||||||
|
if ( ! is_array( $gp_default_sort ) ) {
|
||||||
|
$gp_default_sort = array(
|
||||||
|
'by' => 'priority',
|
||||||
|
'how' => 'desc',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$gp_external_translations = get_user_option( 'gp_external_translations' );
|
||||||
|
|
||||||
|
$openai_key = trim( gp_array_get( $gp_default_sort, 'openai_api_key' ) );
|
||||||
|
$openai_response = null;
|
||||||
|
if ( $openai_key ) {
|
||||||
|
$openai_response = wp_remote_get(
|
||||||
|
'https://api.openai.com/v1/usage?date=' . gmdate( 'Y-m-d' ),
|
||||||
|
array(
|
||||||
|
'timeout' => 8,
|
||||||
|
'headers' => array(
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'Authorization' => 'Bearer ' . $openai_key,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$openai_response_code = wp_remote_retrieve_response_code( $openai_response );
|
||||||
|
|
||||||
|
$deepl_key = trim( gp_array_get( $gp_default_sort, 'deepl_api_key' ) );
|
||||||
|
$deepl_response = null;
|
||||||
|
$deepl_url_free = 'https://api-free.deepl.com/v2/usage';
|
||||||
|
$deepl_url_pro = 'https://api.deepl.com/v2/usage';
|
||||||
|
$deepl_url = gp_array_get( $gp_default_sort, 'deepl_use_api_pro', false ) ? $deepl_url_pro : $deepl_url_free;
|
||||||
|
|
||||||
|
if ( $deepl_key ) {
|
||||||
|
$deepl_response = wp_remote_get(
|
||||||
|
$deepl_url,
|
||||||
|
array(
|
||||||
|
'timeout' => 4,
|
||||||
|
'headers' => array(
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'Authorization' => 'DeepL-Auth-Key ' . $deepl_key,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$deepl_response_code = wp_remote_retrieve_response_code( $deepl_response );
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<table class="form-table">
|
||||||
|
<tr>
|
||||||
|
<th><label for="per_page"><?php esc_html_e( 'Number of items per page:', 'glotpress' ); ?></label></th>
|
||||||
|
<td><input type="number" id="per_page" name="per_page" value="<?php echo esc_attr( $gp_per_page ); ?>"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="default_sort[by]"><?php esc_html_e( 'Default Sort By:', 'glotpress' ); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
$sort_bys = wp_list_pluck( gp_get_sort_by_fields(), 'title' );
|
||||||
|
|
||||||
|
echo gp_radio_buttons( 'default_sort[by]', $sort_bys, gp_array_get( $gp_default_sort, 'by', 'priority' ) );
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="default_sort[how]"><?php esc_html_e( 'Default Sort Order:', 'glotpress' ); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
echo gp_radio_buttons(
|
||||||
|
'default_sort[how]',
|
||||||
|
array(
|
||||||
|
'asc' => __( 'Ascending', 'glotpress' ),
|
||||||
|
'desc' => __( 'Descending', 'glotpress' ),
|
||||||
|
),
|
||||||
|
gp_array_get( $gp_default_sort, 'how', 'desc' )
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- Including the "notifications_optin" in the "default_sort" array is a hack.
|
||||||
|
If we include it in the future in the GlotPress core, it would be interesting to put
|
||||||
|
this value in it own option item.
|
||||||
|
I do this because the post values are processed in the GP_Route_Settings->settings_post,
|
||||||
|
and I have to modify the GlotPress core to add a new configuration item. -->
|
||||||
|
<tr>
|
||||||
|
<th><label for="default_sort[notifications_optin]"><?php esc_html_e( 'I want to receive notifications of discussions:', 'glotpress' ); ?></label></th>
|
||||||
|
<td><input type="checkbox" id="default_sort[notifications_optin]" name="default_sort[notifications_optin]" <?php gp_checked( 'on' == gp_array_get( $gp_default_sort, 'notifications_optin', 'off' ) ); ?> /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<h4><?php esc_html_e( 'External translating services', 'glotpress' ); ?></h4>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="default_sort[external_services_exclude_some_status]"><?php esc_html_e( 'Don\'t use OpenAI and DeepL with translations in current, rejected or old status.', 'glotpress' ); ?></label></th>
|
||||||
|
<td><input type="checkbox" id="default_sort[external_services_exclude_some_status]" name="default_sort[external_services_exclude_some_status]" <?php gp_checked( 'on' == gp_array_get( $gp_default_sort, 'external_services_exclude_some_status', 'off' ) ); ?> /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<h4><?php esc_html_e( 'OpenAI (ChatGPT) settings', 'glotpress' ); ?></h4>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="default_sort[openai_model]"><?php esc_html_e( 'OpenAI model', 'glotpress' ); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<select name="default_sort[openai_model]" id="default_sort[openai_model]" style="border:revert;">
|
||||||
|
<?php
|
||||||
|
$openai_models = array( 'gpt-3.5-turbo', 'gpt-4', 'gpt-4-turbo', 'gpt-4o', 'gpt-4o-mini' );
|
||||||
|
$openai_model = gp_array_get( $gp_default_sort, 'openai_model', 'gpt-3.5-turbo' );
|
||||||
|
foreach ( $openai_models as $model ) {
|
||||||
|
echo '<option value="' . esc_attr( $model ) . '"';
|
||||||
|
selected( $model, $openai_model );
|
||||||
|
echo '>' . esc_html( $model ) . '</option>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="default_sort[openai_api_key]">
|
||||||
|
<?php esc_html_e( 'OpenAI API Key', 'glotpress' ); ?>
|
||||||
|
</label>
|
||||||
|
<?php
|
||||||
|
if ( gp_array_get( $gp_external_translations, 'openai_translations_used', 0 ) > 0 ) {
|
||||||
|
echo '<br>';
|
||||||
|
echo '<small>';
|
||||||
|
/* translators: Number of OpenAI translations used. */
|
||||||
|
echo esc_html( sprintf( _n( '%s OpenAI translation used:', '%s OpenAI translations used:', 'glotpress' ), number_format_i18n( gp_array_get( $gp_external_translations, 'openai_translations_used', 0 ) ) ) );
|
||||||
|
if ( gp_array_get( $gp_external_translations, 'openai_same_translations_used', 0 ) > 0 ) {
|
||||||
|
echo ' ' . esc_html(
|
||||||
|
sprintf(
|
||||||
|
/* translators: 1: Number of OpenAI translations used with modifications. 2: Number of OpenAI translations used without modifications. */
|
||||||
|
__( '%1$s with modifications and %2$s without modifications.', 'glotpress' ),
|
||||||
|
number_format_i18n( gp_array_get( $gp_external_translations, 'openai_translations_used', 0 ) - gp_array_get( $gp_external_translations, 'openai_same_translations_used', 0 ) ),
|
||||||
|
number_format_i18n( gp_array_get( $gp_external_translations, 'openai_same_translations_used', 0 ) ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo '</small>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<br>
|
||||||
|
<a href="https://platform.openai.com/account/usage" target="_blank"><small>
|
||||||
|
<?php
|
||||||
|
$openai_tokens_used = gp_array_get( $gp_external_translations, 'openai_tokens_used', 0 );
|
||||||
|
if ( $openai_tokens_used > 0 ) {
|
||||||
|
/* translators: Number of OpenAI tokens used. */
|
||||||
|
echo esc_html( sprintf( __( 'OpenAI tokens used: %s', 'glotpress' ), number_format_i18n( $openai_tokens_used ) ) );
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</small></a>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" class="openai_api_key" id="default_sort[openai_api_key]" name="default_sort[openai_api_key]" value="<?php echo esc_html( gp_array_get( $gp_default_sort, 'openai_api_key', '' ) ); ?>" placeholder="Enter your OpenAI API key" />
|
||||||
|
<?php
|
||||||
|
if ( trim( $openai_key ) ) {
|
||||||
|
echo '<br>';
|
||||||
|
if ( 401 == $openai_response_code ) {
|
||||||
|
echo '<small style="color:red;">';
|
||||||
|
esc_html_e( 'Your OpenAI API Key is not correct.', 'glotpress' );
|
||||||
|
} elseif ( 200 != $openai_response_code ) {
|
||||||
|
echo '<small style="color:red;">';
|
||||||
|
esc_html_e( 'We have had a problem with the OpenAI API.', 'glotpress' );
|
||||||
|
} else {
|
||||||
|
echo '<small style="color:green;">';
|
||||||
|
esc_html_e( 'Your OpenAI API Key is correct.', 'glotpress' );
|
||||||
|
}
|
||||||
|
echo '</small>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="default_sort[openai_custom_prompt]"><?php esc_html_e( 'Custom Prompt', 'glotpress' ); ?></label></th>
|
||||||
|
<td><textarea class="openai_custom_prompt" id="default_sort[openai_custom_prompt]" name="default_sort[openai_custom_prompt]" placeholder="Enter your custom prompt for ChatGPT translation suggestions"><?php echo esc_html( gp_array_get( $gp_default_sort, 'openai_custom_prompt', '' ) ); ?></textarea></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="default_sort[openai_temperature]"><?php esc_html_e( 'Temperature', 'glotpress' ); ?></label></th>
|
||||||
|
<td><input type="number" min="0" max="2" step=".1" class="openai_temperature" id="default_sort[openai_temperature]" name="default_sort[openai_temperature]" value="<?php echo esc_html( gp_array_get( $gp_default_sort, 'openai_temperature', 0 ) ); ?>" placeholder="Enter your OpenAI key" /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<h4><?php esc_html_e( 'DeepL settings', 'glotpress' ); ?></h4>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="default_sort[deepl_use_api_pro]"><?php esc_html_e( 'Use DeepL API Pro', 'glotpress' ); ?></label></th>
|
||||||
|
<td><input type="checkbox" id="default_sort[deepl_use_api_pro]" name="default_sort[deepl_use_api_pro]" <?php gp_checked( 'on' == gp_array_get( $gp_default_sort, 'deepl_use_api_pro', 'off' ) ); ?> /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="default_sort[deepl_api_key]">
|
||||||
|
<?php esc_html_e( 'DeepL API Key', 'glotpress' ); ?>
|
||||||
|
</label>
|
||||||
|
<?php
|
||||||
|
if ( gp_array_get( $gp_external_translations, 'deepl_translations_used', 0 ) > 0 ) {
|
||||||
|
echo '<br>';
|
||||||
|
echo '<small>';
|
||||||
|
/* translators: Number of DeepL translations used. */
|
||||||
|
echo esc_html( sprintf( _n( '%s DeepL translation used:', '%s DeepL translations used:', 'glotpress' ), number_format_i18n( gp_array_get( $gp_external_translations, 'deepl_translations_used', 0 ) ) ) );
|
||||||
|
if ( gp_array_get( $gp_external_translations, 'deepl_same_translations_used', 0 ) > 0 ) {
|
||||||
|
echo ' ' . esc_html(
|
||||||
|
sprintf(
|
||||||
|
/* translators: 1: Number of DeepL translations used with modifications. 2: Number of DeepL translations used without modifications. */
|
||||||
|
__( '%1$s with modifications and %2$s without modifications.', 'glotpress' ),
|
||||||
|
number_format_i18n( gp_array_get( $gp_external_translations, 'deepl_translations_used', 0 ) - gp_array_get( $gp_external_translations, 'deepl_same_translations_used', 0 ) ),
|
||||||
|
number_format_i18n( gp_array_get( $gp_external_translations, 'deepl_same_translations_used', 0 ) ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo '</small>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<br>
|
||||||
|
<a href="https://www.deepl.com/account/usage" target="_blank"><small>
|
||||||
|
<?php
|
||||||
|
$deepl_chars_used = gp_array_get( $gp_external_translations, 'deepl_chars_used', 0 );
|
||||||
|
if ( $deepl_chars_used > 0 ) {
|
||||||
|
/* translators: Number of chars translated with DeepL. */
|
||||||
|
echo esc_html( sprintf( __( 'Chars translated with DeepL: %s', 'glotpress' ), number_format_i18n( $deepl_chars_used ) ) );
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</small></a>
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
<input type="text" class="deepl_api_key" id="default_sort[deepl_api_key]" name="default_sort[deepl_api_key]" value="<?php echo esc_html( gp_array_get( $gp_default_sort, 'deepl_api_key' ) ); ?>" placeholder="Enter your DeepL API key" />
|
||||||
|
<?php
|
||||||
|
if ( trim( $deepl_key ) ) {
|
||||||
|
echo '<br>';
|
||||||
|
if ( 200 != $deepl_response_code ) {
|
||||||
|
echo '<small style="color:red;">';
|
||||||
|
esc_html_e( 'Your DeepL API Key is not correct.', 'glotpress' );
|
||||||
|
} else {
|
||||||
|
echo '<small style="color:green;">';
|
||||||
|
esc_html_e( 'Your DeepL API Key is correct.', 'glotpress' );
|
||||||
|
}
|
||||||
|
echo '</small>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
39
content/plugins/f-translate/templates/settings.php
Normal file
39
content/plugins/f-translate/templates/settings.php
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* The settings page
|
||||||
|
*
|
||||||
|
* Displays the settings page for a user.
|
||||||
|
*
|
||||||
|
* @link http://glotpress.org
|
||||||
|
*
|
||||||
|
* @package GlotPress
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
gp_title( __( 'Your Settings < GlotPress', 'glotpress' ) );
|
||||||
|
gp_breadcrumb( array( __( 'Your Settings', 'glotpress' ) ) );
|
||||||
|
gp_tmpl_header();
|
||||||
|
|
||||||
|
$per_page = (int) get_user_option( 'gp_per_page' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||||
|
if ( 0 === $per_page ) {
|
||||||
|
$per_page = 15; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||||
|
}
|
||||||
|
|
||||||
|
$default_sort = get_user_option( 'gp_default_sort' );
|
||||||
|
if ( ! is_array( $default_sort ) ) {
|
||||||
|
$default_sort = array(
|
||||||
|
'by' => 'priority',
|
||||||
|
'how' => 'desc',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<h2><?php _e( 'Your Settings', 'glotpress' ); ?></h2>
|
||||||
|
<form action="" method="post">
|
||||||
|
<?php require_once __DIR__ . '/settings-edit.php'; ?>
|
||||||
|
<br>
|
||||||
|
<?php gp_route_nonce_field( 'update-settings_' . get_current_user_id() ); ?>
|
||||||
|
<input type="submit" name="submit" value="<?php esc_attr_e( 'Save Settings', 'glotpress' ); ?>">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
gp_tmpl_footer();
|
104
content/plugins/f-translate/templates/stats-overview.php
Normal file
104
content/plugins/f-translate/templates/stats-overview.php
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
<?php
|
||||||
|
gp_title( __( 'Translation status overview < GlotPress' ) );
|
||||||
|
gp_enqueue_script( 'tablesorter' );
|
||||||
|
|
||||||
|
$breadcrumb = array();
|
||||||
|
$breadcrumb[] = gp_link_get( '/', __( 'Locales' ) );
|
||||||
|
$breadcrumb[] = __( 'Translation status overview' );
|
||||||
|
gp_breadcrumb( $breadcrumb );
|
||||||
|
gp_tmpl_header();
|
||||||
|
|
||||||
|
?>
|
||||||
|
<div class="stats-table">
|
||||||
|
<style>
|
||||||
|
.gp-content {
|
||||||
|
max-width: 95%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<table id="stats-table" class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="col-locale-code"><?php _e( 'Locale' ); ?></th>
|
||||||
|
<?php foreach ( $projects as $slug => $project ) :
|
||||||
|
$name = str_replace( array( 'WordPress.org ', 'WordPress for ', 'WordPress ', 'ectory', ' - Development' ), '', $project->name );
|
||||||
|
if ( $slug == 'wp-plugins' || $slug == 'wp-themes' ) {
|
||||||
|
$name = "Waiting $name";
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<th class="col-<?php echo esc_attr( sanitize_title( $name ) ); ?>"><?php echo esc_html( $name ); ?></th>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
foreach ( $translation_locale_complete as $locale_slug => $total_complete ) :
|
||||||
|
$gp_locale = GP_Locales::by_slug( $locale_slug );
|
||||||
|
|
||||||
|
if ( ! $gp_locale || ! $gp_locale->wp_locale ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
list( $locale, $set_slug ) = array_merge( explode( '/', $locale_slug ), [ 'default' ] );
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<th title="<?php echo esc_attr( $gp_locale->english_name ); ?>">
|
||||||
|
<a href="<?php echo esc_url( gp_url( gp_url_join( 'locale', $locale, $set_slug ) ) ); ?>">
|
||||||
|
<?php echo esc_html( $gp_locale->wp_locale ); ?>
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<?php
|
||||||
|
foreach ( $projects as $slug => $project ) {
|
||||||
|
$projecturl = gp_url( gp_url_join( 'locale', $locale, $set_slug, $project->path ) );
|
||||||
|
$project_name = str_replace( array( 'WordPress.org ', 'WordPress for ', 'WordPress ', 'ectory' ), '', $project->name );
|
||||||
|
|
||||||
|
if ( isset( $translation_locale_statuses[ $locale_slug ][ $project->path ] ) ) {
|
||||||
|
$percent = $translation_locale_statuses[ $locale_slug ][ $project->path ];
|
||||||
|
|
||||||
|
if ( 'waiting' === $project->path || 'wp-plugins' === $project->path || 'wp-themes' === $project->path ) {
|
||||||
|
$project_link_title = '';
|
||||||
|
if ( 'wp-plugins' === $project->path || 'wp-themes' === $project->path ) {
|
||||||
|
$project_link_title = "Last Updated {$project->cache_last_updated}";
|
||||||
|
|
||||||
|
// Filter Plugins/Themes to Waiting (Most first) - Relying upon these being the last items.
|
||||||
|
$projecturl = add_query_arg( 'filter', 'strings-waiting-and-fuzzy', $projecturl );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color code it on -0~500 waiting strings
|
||||||
|
$percent_class = 100-min( (int) ( $percent / 50 ) * 10, 100 );
|
||||||
|
// It's only 100 if it has 0 strings.
|
||||||
|
if ( 100 == $percent_class && $percent ) {
|
||||||
|
$percent_class = 90;
|
||||||
|
}
|
||||||
|
$percent_class = 'percent' . $percent_class;
|
||||||
|
echo '<td data-column-title="' . esc_attr( $project_name ) . '" data-sort-value="'. esc_attr( $percent ) . '" class="' . $percent_class .'"><a href="' . esc_url( $projecturl ) . '" title="' . esc_attr( $project_link_title ) . '">' . number_format( $percent ) . '</a></td>';
|
||||||
|
} else {
|
||||||
|
$percent_class = 'percent' . (int) ( $percent / 10 ) * 10;
|
||||||
|
echo '<td data-column-title="' . esc_attr( $project_name ) . '" data-sort-value="' . esc_attr( $percent ) . '" class="' . $percent_class .'"><a href="' . esc_url( $projecturl ) . '">' . $percent . '%</a></td>';
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
echo '<td class="none" data-column-title="" data-sort-value="-1">—</td>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery( document ).ready( function( $ ) {
|
||||||
|
$( '#stats-table' ).tablesorter( {
|
||||||
|
theme: 'wporg-translate',
|
||||||
|
textExtraction: function( node ) {
|
||||||
|
var cellValue = $( node ).text(),
|
||||||
|
sortValue = $( node ).data( 'sortValue' );
|
||||||
|
|
||||||
|
return ( undefined !== sortValue ) ? sortValue : cellValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
gp_tmpl_footer();
|
|
@ -0,0 +1,102 @@
|
||||||
|
<?php
|
||||||
|
gp_title( __( 'Translation status overview < GlotPress' ) );
|
||||||
|
gp_enqueue_script( 'tablesorter' );
|
||||||
|
|
||||||
|
$breadcrumb = array();
|
||||||
|
$breadcrumb[] = gp_link_get( '/', __( 'Locales' ) );
|
||||||
|
$breadcrumb[] = gp_link_get( gp_url_join( '/locale', $locale_path ), esc_html( $gp_locale->english_name ) );
|
||||||
|
$breadcrumb[] = trim( ucwords( $view ), 's' ) . ' translation status overview';
|
||||||
|
gp_breadcrumb( $breadcrumb );
|
||||||
|
gp_tmpl_header();
|
||||||
|
|
||||||
|
$columns = array(
|
||||||
|
'all' => __( 'Translated Percent' ),
|
||||||
|
'current' => __( 'Translated' ),
|
||||||
|
'untranslated' => __( 'Untranslated' ),
|
||||||
|
'fuzzy' => __( 'Fuzzy' ),
|
||||||
|
'waiting' => __( 'Waiting' ),
|
||||||
|
);
|
||||||
|
$main_column_title = trim( ucwords( $view ), 's' );
|
||||||
|
|
||||||
|
?>
|
||||||
|
<div class="stats-table">
|
||||||
|
<table id="stats-table" class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><span class="with-tooltip" aria-label="Sorted by active installations"><?php echo $main_column_title; ?></span></th>
|
||||||
|
<?php
|
||||||
|
foreach ( $columns as $title ) {
|
||||||
|
printf( "<th>%s</th>", $title );
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
foreach ( $items as $slug => $details ) {
|
||||||
|
$project_link = gp_url( gp_url_join( 'locale', $locale_path, $details->project->path ) );
|
||||||
|
// Themes get a shortcut..
|
||||||
|
if ( 'themes' == $view ) {
|
||||||
|
$project_link = gp_url( gp_url_join( 'projects', $details->project->path, $locale_path ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$project_overview_link = gp_url( gp_url_join( 'projects', $details->project->path ) );
|
||||||
|
|
||||||
|
echo '<tr>';
|
||||||
|
printf(
|
||||||
|
'<th title="%s" data-column-title="%s" data-sort-value="%s"><a href="%s">%s</a></th>',
|
||||||
|
esc_attr( sprintf( __( "%s+ Active Installations" ), number_format_i18n( $details->installs ) ) ),
|
||||||
|
esc_attr( $main_column_title ),
|
||||||
|
$details->installs,
|
||||||
|
$project_overview_link,
|
||||||
|
$details->project->name
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $columns as $field => $title ) {
|
||||||
|
$sort_value = $stat_value = ( $details->stats->{$field} ?? 0 );
|
||||||
|
$percent = $stat_value / $details->stats->all * 100;
|
||||||
|
$link = $project_link;
|
||||||
|
$cell_text = number_format_i18n( $stat_value );
|
||||||
|
|
||||||
|
if ( in_array( $field, [ 'fuzzy', 'untranslated', 'waiting' ] ) ) {
|
||||||
|
$percent = 100 - $percent;
|
||||||
|
$link = add_query_arg( 'filters[status]', $field, $project_link );
|
||||||
|
} elseif ( 'all' == $field ) {
|
||||||
|
$percent = ($details->stats->current ?? 0) / $details->stats->all * 100;
|
||||||
|
$sort_value = $percent;
|
||||||
|
$cell_text = ( $percent > 50 ? floor( $percent ) : ceil( $percent ) ) . '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
$percent_class = 'percent' . (int) ( $percent / 10 ) * 10;
|
||||||
|
|
||||||
|
printf( '<td class="%s" data-column-title="%s" data-sort-value="%s"><a href="%s">%s</a></td>',
|
||||||
|
$percent_class,
|
||||||
|
$title,
|
||||||
|
$sort_value,
|
||||||
|
$link,
|
||||||
|
$cell_text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
echo '</tr>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery( document ).ready( function( $ ) {
|
||||||
|
$( '#stats-table' ).tablesorter( {
|
||||||
|
theme: 'wporg-translate',
|
||||||
|
textExtraction: function( node ) {
|
||||||
|
var cellValue = $( node ).text(),
|
||||||
|
sortValue = $( node ).data( 'sortValue' );
|
||||||
|
|
||||||
|
return ( undefined !== sortValue ) ? sortValue : cellValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<?php
|
||||||
|
gp_tmpl_footer();
|
3125
content/plugins/f-translate/templates/style.css
Normal file
3125
content/plugins/f-translate/templates/style.css
Normal file
File diff suppressed because it is too large
Load diff
398
content/plugins/f-translate/templates/translation-row-editor.php
Normal file
398
content/plugins/f-translate/templates/translation-row-editor.php
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Template for a single translation row editor in a translation set display.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
$more_links = array();
|
||||||
|
if ( $translation->translation_status ) {
|
||||||
|
$translation_permalink = gp_url_project_locale(
|
||||||
|
$project,
|
||||||
|
$locale->slug,
|
||||||
|
$translation_set->slug,
|
||||||
|
array(
|
||||||
|
'filters[status]' => 'either',
|
||||||
|
'filters[original_id]' => $translation->original_id,
|
||||||
|
'filters[translation_id]' => $translation->id,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$more_links['translation-permalink'] = '<a href="' . esc_url( $translation_permalink ) . '">Permalink to translation</a>';
|
||||||
|
} else {
|
||||||
|
$original_permalink = gp_url_project_locale( $project, $locale->slug, $translation_set->slug, array( 'filters[original_id]' => $translation->original_id ) );
|
||||||
|
$more_links['original-permalink'] = '<a href="' . esc_url( $original_permalink ) . '">Permalink to original</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$original_history = gp_url_project_locale(
|
||||||
|
$project,
|
||||||
|
$locale->slug,
|
||||||
|
$translation_set->slug,
|
||||||
|
array(
|
||||||
|
'filters[status]' => 'either',
|
||||||
|
'filters[original_id]' => $translation->original_id,
|
||||||
|
'sort[by]' => 'translation_date_added',
|
||||||
|
'sort[how]' => 'asc',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$more_links['history'] = '<a href="' . esc_url( $original_history ) . '">Translation History</a>';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to modify the more links in the translation editor.
|
||||||
|
*
|
||||||
|
* @since 2.3.0
|
||||||
|
*
|
||||||
|
* @param array $more_links The links to be output.
|
||||||
|
* @param GP_Project $project Project object.
|
||||||
|
* @param GP_Locale $locale Locale object.
|
||||||
|
* @param GP_Translation_Set $translation_set Translation Set object.
|
||||||
|
* @param GP_Translation $translation Translation object.
|
||||||
|
*/
|
||||||
|
$more_links = apply_filters( 'gp_translation_row_template_more_links', $more_links, $project, $locale, $translation_set, $translation );
|
||||||
|
?>
|
||||||
|
<tr class="editor <?php echo gp_translation_row_classes( $translation ); ?>" id="editor-<?php echo esc_attr( $translation->row_id ); ?>" row="<?php echo esc_attr( $translation->row_id ); ?>">
|
||||||
|
<td colspan="<?php echo $can_approve ? 5 : 4; ?>">
|
||||||
|
<div class="editor-panel">
|
||||||
|
<div class="editor-panel__left">
|
||||||
|
<div class="panel-header">
|
||||||
|
<?php
|
||||||
|
$status = sprintf(
|
||||||
|
'<span class="panel-header__bubble%s">%s</span>',
|
||||||
|
$translation->translation_status ? ' panel-header__bubble--' . $translation->translation_status : '',
|
||||||
|
display_status( $translation->translation_status )
|
||||||
|
);
|
||||||
|
|
||||||
|
$warnings_count = wporg_gp_count_warnings( $translation );
|
||||||
|
$warnings_info = '';
|
||||||
|
if ( $warnings_count ) {
|
||||||
|
$warnings_info = ' <span class="panel-header__bubble panel-header__bubble--warning">' . sprintf(
|
||||||
|
_n( '%s warning', '%s warnings', $warnings_count ),
|
||||||
|
number_format_i18n( $warnings_count )
|
||||||
|
) . '</span>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<h3>Original <?php echo $status . $warnings_info; ?></h3>
|
||||||
|
<div class="panel-header-actions">
|
||||||
|
<button type="button" class="panel-header-actions__cancel with-tooltip" aria-label="Close current editor">
|
||||||
|
<span class="screen-reader-text">Close</span><span aria-hidden="true" class="dashicons dashicons-no-alt"></span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="panel-header-actions__previous with-tooltip" aria-label="Open previous editor">
|
||||||
|
<span class="screen-reader-text">Previous</span><span aria-hidden="true" class="dashicons dashicons-arrow-up-alt2"></span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="panel-header-actions__next with-tooltip" aria-label="Open next editor">
|
||||||
|
<span class="screen-reader-text">Next</span><span aria-hidden="true" class="dashicons dashicons-arrow-down-alt2"></span>
|
||||||
|
</button>
|
||||||
|
<div class="button-menu">
|
||||||
|
<button type="button" class="button-menu__toggle with-tooltip" aria-label="Show contextual links">
|
||||||
|
<span class="screen-reader-text">Links</span><span aria-hidden="true" class="dashicons dashicons-menu-alt"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="button-menu__dropdown">
|
||||||
|
<?php foreach ( $more_links as $link ) : ?>
|
||||||
|
<li><?php echo $link; ?></li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<div class="source-string strings">
|
||||||
|
<?php
|
||||||
|
$singular = $translation->singular_glossary_markup ?? esc_translation( $translation->singular );
|
||||||
|
$plural = $translation->plural_glossary_markup ?? esc_translation( $translation->plural );
|
||||||
|
|
||||||
|
if ( ! $translation->plural ) :
|
||||||
|
?>
|
||||||
|
<div class="source-string__singular">
|
||||||
|
<span class="original"><?php echo prepare_original( $singular ); ?></span>
|
||||||
|
<span aria-hidden="true" class="original-raw"><?php echo esc_translation( $translation->singular ); ?></span>
|
||||||
|
</div>
|
||||||
|
<?php else : ?>
|
||||||
|
<div class="source-string__singular">
|
||||||
|
<small>Singular:</small>
|
||||||
|
<span class="original"><?php echo prepare_original( $singular ); ?></span>
|
||||||
|
<span aria-hidden="true" class="original-raw"><?php echo esc_translation( $translation->singular ); ?></span>
|
||||||
|
</div>
|
||||||
|
<div class="source-string__plural">
|
||||||
|
<small>Plural:</small>
|
||||||
|
<span class="original"><?php echo prepare_original( $plural ); ?></span>
|
||||||
|
<span aria-hidden="true" class="original-raw"><?php echo esc_translation( $translation->plural ); ?></span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="source-details">
|
||||||
|
<?php if ( wporg_gp_should_display_original_context( $translation ) ) : ?>
|
||||||
|
<details open class="source-details__context">
|
||||||
|
<summary>Context</summary>
|
||||||
|
<span class="context bubble"><?php echo esc_translation( $translation->context ); ?></span>
|
||||||
|
</details>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php
|
||||||
|
if ( $translation->extracted_comments ) :
|
||||||
|
?>
|
||||||
|
<details open class="source-details__comment">
|
||||||
|
<summary><?php _e( 'Comment', 'glotpress' ); ?></summary>
|
||||||
|
<p>
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Filters the extracted comments of an original.
|
||||||
|
*
|
||||||
|
* @param string $extracted_comments Extracted comments of an original.
|
||||||
|
* @param GP_Translation $translation Translation object.
|
||||||
|
*/
|
||||||
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||||
|
echo apply_filters( 'gp_original_extracted_comments', $translation->extracted_comments, $translation );
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
</details>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ( $translation->references ) : ?>
|
||||||
|
<details class="source-details__references">
|
||||||
|
<summary>References</summary>
|
||||||
|
<?php wporg_references( $project, $translation ); ?>
|
||||||
|
</details>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="translation-wrapper">
|
||||||
|
<?php if ( $translation->plural && $locale->nplurals > 1 ) : ?>
|
||||||
|
<div class="translation-form-wrapper">
|
||||||
|
<span>Form:</span>
|
||||||
|
<ul class="translation-form-list">
|
||||||
|
<?php if ( 2 === (int) $locale->nplurals && 'n != 1' === $locale->plural_expression ) : ?>
|
||||||
|
<li>
|
||||||
|
<button class="translation-form-list__tab translation-form-list__tab--active with-tooltip"
|
||||||
|
type="button"
|
||||||
|
aria-label="Translation for singular form"
|
||||||
|
data-plural-index="0">
|
||||||
|
Singular
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="translation-form-list__tab with-tooltip"
|
||||||
|
type="button"
|
||||||
|
aria-label="Translation for plural form"
|
||||||
|
data-plural-index="1">
|
||||||
|
Plural
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<?php else : ?>
|
||||||
|
<?php
|
||||||
|
foreach ( range( 0, $locale->nplurals - 1 ) as $plural_index ) :
|
||||||
|
$plural_string = implode( ', ', $locale->numbers_for_index( $plural_index ) );
|
||||||
|
?>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
class="translation-form-list__tab with-tooltip<?php echo ( 0 === $plural_index ) ? ' translation-form-list__tab--active' : ''; ?>"
|
||||||
|
data-plural-index="<?php echo $plural_index; ?>"
|
||||||
|
aria-label="<?php printf( 'This plural form is used for numbers like: %s', $plural_string ); ?>"
|
||||||
|
type="button">
|
||||||
|
<?php echo $plural_string; ?>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php if ( ! $translation->plural ) : ?>
|
||||||
|
<?php wporg_gp_translate_textarea( $translation, array( $can_edit, $can_approve_translation ) ); ?>
|
||||||
|
<?php else : ?>
|
||||||
|
<?php foreach ( range( 0, $locale->nplurals - 1 ) as $plural_index ) : ?>
|
||||||
|
<?php wporg_gp_translate_textarea( $translation, array( $can_edit, $can_approve ), $plural_index ); ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="translation-actions">
|
||||||
|
<?php if ( $can_edit ) : ?>
|
||||||
|
<div class="translation-actions__primary">
|
||||||
|
<button class="button is-primary translation-actions__save with-tooltip"
|
||||||
|
type="button"
|
||||||
|
aria-label="<?php echo $can_approve_translation ? 'Save and approve translation' : 'Suggest new translation'; ?>"
|
||||||
|
data-nonce="<?php echo esc_attr( wp_create_nonce( 'add-translation_' . $translation->original_id ) ); ?>">
|
||||||
|
<?php echo $can_approve_translation ? 'Save' : 'Suggest'; ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="translation-actions__secondary">
|
||||||
|
<button type="button" class="button is-small translation-actions__copy with-tooltip" aria-label="Copy original">
|
||||||
|
<span class="screen-reader-text">Copy</span><span aria-hidden="true" class="dashicons dashicons-admin-page"></span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="button is-small translation-actions__insert-tab with-tooltip" aria-label="Insert tab">
|
||||||
|
<span class="screen-reader-text">Insert tab</span><span aria-hidden="true" class="dashicons dashicons-editor-indent"></span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="button is-small translation-actions__insert-newline with-tooltip" aria-label="Insert newline">
|
||||||
|
<span class="screen-reader-text">Insert newline</span><span aria-hidden="true" class="dashicons dashicons-editor-break"></span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="button is-small translation-actions__ltr with-tooltip" aria-label="Switch to LTR">
|
||||||
|
<span class="screen-reader-text">LTR</span><span aria-hidden="true" class="dashicons dashicons-editor-ltr"></span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="button is-small translation-actions__rtl with-tooltip" aria-label="Switch to RTL">
|
||||||
|
<span class="screen-reader-text">RTL</span><span aria-hidden="true" class="dashicons dashicons-editor-rtl"></span>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="button is-small translation-actions__help with-tooltip" aria-label="Show help">
|
||||||
|
<span class="screen-reader-text">Help</span><span aria-hidden="true" class="dashicons dashicons-editor-help"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php elseif ( is_user_logged_in() ) : ?>
|
||||||
|
<?php echo apply_filters( 'wporg_translate_cannot_edit_translation_hint', '<p class="info">Sorry, you are not allowed to edit this translation.</p>', $translation ); ?>
|
||||||
|
<?php else : ?>
|
||||||
|
<p class="info">
|
||||||
|
<?php
|
||||||
|
printf(
|
||||||
|
'You <a href="%s">have to log in</a> to edit this translation.',
|
||||||
|
esc_url( wp_login_url( gp_url_current() ) )
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ( $can_edit && has_action( 'wporg_translate_suggestions' ) ) {
|
||||||
|
?>
|
||||||
|
<div class="suggestions-wrapper">
|
||||||
|
<?php do_action( 'wporg_translate_suggestions', $translation ); ?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="editor-panel__right">
|
||||||
|
<!-- <div class="panel-header">
|
||||||
|
<h3><?php /*_e( 'Meta', 'glotpress' ); */ ?></h3>
|
||||||
|
</div>-->
|
||||||
|
<div class="panel-content">
|
||||||
|
<?php ob_start(); ?>
|
||||||
|
<div class="meta" id="sidebar-div-meta-<?php echo $translation->row_id; ?>">
|
||||||
|
<?php gp_tmpl_load( 'translation-row-editor-meta-feedback', get_defined_vars() ); ?>
|
||||||
|
<?php if ( $translation->translation_status && ( $can_approve_translation || $can_reject_self ) ) : ?>
|
||||||
|
<div class="status-actions">
|
||||||
|
<?php if ( $can_approve_translation ) : ?>
|
||||||
|
<?php if ( 'current' !== $translation->translation_status ) : ?>
|
||||||
|
<button class="button is-primary approve" tabindex="-1" data-nonce="<?php echo esc_attr( wp_create_nonce( 'update-translation-status-current_' . $translation->id ) ); ?>"><strong>+</strong> <?php _e( 'Approve', 'glotpress' ); ?></button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ( 'rejected' !== $translation->translation_status ) : ?>
|
||||||
|
<button class="button reject" tabindex="-1" data-nonce="<?php echo esc_attr( wp_create_nonce( 'update-translation-status-rejected_' . $translation->id ) ); ?>"><strong>−</strong> <?php _e( 'Reject', 'glotpress' ); ?></button>
|
||||||
|
<?php if ( apply_filters( 'gp_enable_changesrequested_status', false ) ) : // todo: delete when we merge the gp-translation-helpers in GlotPress ?>
|
||||||
|
<button class="button changesrequested" style="display: none;" data-nonce="<?php echo esc_attr( wp_create_nonce( 'update-translation-status-changesrequested_' . $translation->id ) ); ?>" title="<?php esc_attr_e( 'Request changes for this translation. The existing translation will be kept as part of the translation history.', 'glotpress' ); ?>"><strong>−</strong> <?php _ex( 'Request changes', 'Action', 'glotpress' ); ?></button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ( 'fuzzy' !== $translation->translation_status ) : ?>
|
||||||
|
<button class="button fuzzy" tabindex="-1" data-nonce="<?php echo esc_attr( wp_create_nonce( 'update-translation-status-fuzzy_' . $translation->id ) ); ?>"><strong>~</strong> <?php _e( 'Fuzzy', 'glotpress' ); ?></button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php elseif ( $can_reject_self ) : ?>
|
||||||
|
<button class="button reject" tabindex="-1" data-nonce="<?php echo esc_attr( wp_create_nonce( 'update-translation-status-rejected_' . $translation->id ) ); ?>"><strong>−</strong> <?php _e( 'Reject Suggestion', 'glotpress' ); ?></button>
|
||||||
|
<button class="button fuzzy" tabindex="-1" data-nonce="<?php echo esc_attr( wp_create_nonce( 'update-translation-status-fuzzy_' . $translation->id ) ); ?>"><strong>~</strong> <?php _e( 'Fuzzy', 'glotpress' ); ?></button>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<dt><?php _e( 'Status:', 'glotpress' ); ?></dt>
|
||||||
|
<dd>
|
||||||
|
<?php echo display_status( $translation->translation_status ); ?>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<?php if ( $translation->translation_added && $translation->translation_added !== '0000-00-00 00:00:00' ) : ?>
|
||||||
|
<dl>
|
||||||
|
<dt><?php _e( 'Added:', 'glotpress' ); ?></dt>
|
||||||
|
<dd><?php echo $translation->translation_added; ?> UTC</dd>
|
||||||
|
</dl>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ( $translation->date_modified && $translation->date_modified !== '0000-00-00 00:00:00' && $translation->date_modified !== $translation->translation_added ) : ?>
|
||||||
|
<dl>
|
||||||
|
<dt><?php _e( 'Last modified:', 'glotpress' ); ?></dt>
|
||||||
|
<dd><?php echo $translation->date_modified; ?> UTC</dd>
|
||||||
|
</dl>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ( $translation->user ) : ?>
|
||||||
|
<dl>
|
||||||
|
<dt><?php _e( 'Translated by:', 'glotpress' ); ?></dt>
|
||||||
|
<dd><?php gp_link_user( $translation->user ); ?></dd>
|
||||||
|
</dl>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ( $translation->user_last_modified && ( ! $translation->user || $translation->user->ID !== $translation->user_last_modified->ID ) ) : ?>
|
||||||
|
<dl>
|
||||||
|
<dt>
|
||||||
|
<?php
|
||||||
|
if ( 'current' === $translation->translation_status ) {
|
||||||
|
_e( 'Approved by:', 'glotpress' );
|
||||||
|
} elseif ( 'rejected' === $translation->translation_status ) {
|
||||||
|
_e( 'Rejected by:', 'glotpress' );
|
||||||
|
} else {
|
||||||
|
_e( 'Last updated by:', 'glotpress' );
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</dt>
|
||||||
|
<dd><?php gp_link_user( $translation->user_last_modified ); ?></dd>
|
||||||
|
</dl>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<dt><?php _e( 'Priority of the original:', 'glotpress' ); ?></dt>
|
||||||
|
<?php if ( $can_write ) : ?>
|
||||||
|
<dd>
|
||||||
|
<?php
|
||||||
|
echo gp_select(
|
||||||
|
'priority-' . $translation->original_id,
|
||||||
|
GP::$original->get_static( 'priorities' ),
|
||||||
|
$translation->priority,
|
||||||
|
array(
|
||||||
|
'class' => 'priority',
|
||||||
|
'tabindex' => '-1',
|
||||||
|
'data-nonce' => wp_create_nonce( 'set-priority_' . $translation->original_id ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
</dd>
|
||||||
|
<?php else : ?>
|
||||||
|
<dd><?php echo gp_array_get( GP::$original->get_static( 'priorities' ), $translation->priority, 'unknown' ); ?></dd>
|
||||||
|
<?php endif; ?>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
$meta_sidebar = ob_get_clean();
|
||||||
|
|
||||||
|
$sidebar_tabs = '<nav class="nav-sidebar">';
|
||||||
|
$sidebar_tabs .= '<ul class="sidebar-tabs">';
|
||||||
|
$sidebar_tabs .= ' <li class="current tab-meta" data-tab="sidebar-tab-meta-' . $translation->row_id . '" data-row-id="' . $translation->row_id . '">Meta</li>';
|
||||||
|
$sidebar_tabs .= ' <li class="tab-discussion" data-tab="sidebar-tab-discussion-' . $translation->row_id . '" data-row-id="' . $translation->row_id . '">Discussion <span class="count"></span></li>';
|
||||||
|
$sidebar_tabs .= ' <li class="tab-others" data-tab="sidebar-tab-others-' . $translation->row_id . '" data-row-id="' . $translation->row_id . '">Others <span class="count"></span></li>';
|
||||||
|
$sidebar_tabs .= '</ul>';
|
||||||
|
$sidebar_tabs .= $meta_sidebar;
|
||||||
|
$sidebar_tabs .= '<div class="meta discussion" id="sidebar-div-discussion-' . $translation->row_id . '" data-row-id="' . $translation->row_id . '" style="display: none;"></div>';
|
||||||
|
$sidebar_tabs .= '<div class="meta others" id="sidebar-div-others-' . $translation->row_id . '" data-row-id="' . $translation->row_id . '" style="display: none;">';
|
||||||
|
$sidebar_tabs .= ' <details class="details-translation-memory" open="">';
|
||||||
|
$sidebar_tabs .= ' <summary class="summary-translation-memory" id="summary-translation-memory-' . $translation->row_id . '">Translation Memory';
|
||||||
|
$sidebar_tabs .= ' <span aria-hidden="true" class="suggestions__loading-indicator__icon"><span></span><span></span><span></span></span>';
|
||||||
|
$sidebar_tabs .= ' </summary>';
|
||||||
|
$sidebar_tabs .= ' <div class="sidebar-div-others-translation-memory-content" id="sidebar-div-others-translation-memory-content-' . $translation->row_id . '"></div>';
|
||||||
|
$sidebar_tabs .= ' </details>';
|
||||||
|
$sidebar_tabs .= ' <details class="details-other-locales" open="">';
|
||||||
|
$sidebar_tabs .= ' <summary class="summary-other-locales" id="summary-other-locales-' . $translation->row_id . '">Other locales';
|
||||||
|
$sidebar_tabs .= ' <span aria-hidden="true" class="suggestions__loading-indicator__icon"><span></span><span></span><span></span></span>';
|
||||||
|
$sidebar_tabs .= ' </summary>';
|
||||||
|
$sidebar_tabs .= ' <div class="sidebar-div-others-other-locales-content" id="sidebar-div-others-other-locales-content-' . $translation->row_id . '"></div>';
|
||||||
|
$sidebar_tabs .= ' </details>';
|
||||||
|
$sidebar_tabs .= ' <details class="details-history" open="">';
|
||||||
|
$sidebar_tabs .= ' <summary class="summary-history" id="summary-history-' . $translation->row_id . '">History';
|
||||||
|
$sidebar_tabs .= ' <span aria-hidden="true" class="suggestions__loading-indicator__icon"><span></span><span></span><span></span></span>';
|
||||||
|
$sidebar_tabs .= ' </summary>';
|
||||||
|
$sidebar_tabs .= ' <div class="sidebar-div-others-history-content" id="sidebar-div-others-history-content-' . $translation->row_id . '"></div>';
|
||||||
|
$sidebar_tabs .= ' </details>';
|
||||||
|
$sidebar_tabs .= '</div>'; /* meta others */
|
||||||
|
$sidebar_tabs .= '</nav>';
|
||||||
|
|
||||||
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||||
|
echo $sidebar_tabs;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php do_action( 'wporg_translate_meta', $translation ); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
|
@ -0,0 +1,137 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Template for the preview part of a single translation row in a translation set display
|
||||||
|
*/
|
||||||
|
|
||||||
|
$priority_char = array(
|
||||||
|
'-2' => '×',
|
||||||
|
'-1' => '↓',
|
||||||
|
'0' => '',
|
||||||
|
'1' => '↑',
|
||||||
|
);
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<tr class="preview <?php gp_translation_row_classes( $translation ); ?>" id="preview-<?php echo esc_attr( $translation->row_id ); ?>" row="<?php echo esc_attr( $translation->row_id ); ?>">
|
||||||
|
<?php if ( $can_approve_translation ) : ?>
|
||||||
|
<th scope="row" class="checkbox"><input type="checkbox" name="selected-row[]"/></th>
|
||||||
|
<?php elseif ( $can_approve ) : ?>
|
||||||
|
<th scope="row"></th>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php /* translators: %s: Priority of original */ ?>
|
||||||
|
<td class="priority" title="<?php echo esc_attr( sprintf( __( 'Priority: %s', 'glotpress' ), gp_array_get( GP::$original->get_static( 'priorities' ), $translation->priority ) ) ); ?>">
|
||||||
|
<?php
|
||||||
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||||
|
echo $priority_char[ $translation->priority ];
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
<td class="original">
|
||||||
|
<?php
|
||||||
|
if ( ! $translation->plural ) :
|
||||||
|
?>
|
||||||
|
<span class="original-text"><?php echo prepare_original( $translation_singular ); ?></span>
|
||||||
|
<?php
|
||||||
|
else :
|
||||||
|
$translation_plural = isset( $translation->plural_glossary_markup ) ? $translation->plural_glossary_markup : esc_translation( $translation->plural );
|
||||||
|
?>
|
||||||
|
<ul>
|
||||||
|
<li><small>Singular:</small><br><span class="original-text"><?php echo prepare_original( $translation_singular ); ?></span></li>
|
||||||
|
<li><small>Plural:</small><br><span class="original-text"><?php echo prepare_original( $translation_plural ); ?></span></li>
|
||||||
|
</ul>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$show_context = wporg_gp_should_display_original_context( $translation );
|
||||||
|
$show_priority = '1' === $translation->priority || '-1' === $translation->priority;
|
||||||
|
if ( $show_context || $show_priority ) :
|
||||||
|
?>
|
||||||
|
<div class="original-tags">
|
||||||
|
<?php
|
||||||
|
if ( $show_context ) :
|
||||||
|
?>
|
||||||
|
<span class="context bubble"><?php echo esc_html( $translation->context ); ?></span>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
|
|
||||||
|
if ( $show_priority ) :
|
||||||
|
?>
|
||||||
|
<span class="priority bubble"><?php echo esc_html( sprintf( 'Priority: %s', gp_array_get( GP::$original->get_static( 'priorities' ), $translation->priority ) ) ); ?></span>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
endif;
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
<td class="translation foreign-text">
|
||||||
|
<?php
|
||||||
|
if ( $can_edit ) {
|
||||||
|
$edit_text = __( 'Double-click to add', 'glotpress' );
|
||||||
|
} elseif ( is_user_logged_in() ) {
|
||||||
|
$edit_text = __( 'You are not allowed to add a translation.', 'glotpress' );
|
||||||
|
} else {
|
||||||
|
/* translators: %s: url */
|
||||||
|
$edit_text = sprintf( __( 'You <a href="%s">have to log in</a> to add a translation.', 'glotpress' ), esc_url( wp_login_url( gp_url_current() ) ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$missing_text = "<span class='missing'>$edit_text</span>";
|
||||||
|
if ( ! count( array_filter( $translation->translations, 'gp_is_not_null' ) ) ) :
|
||||||
|
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||||
|
echo $missing_text;
|
||||||
|
elseif ( ! $translation->plural || 1 === $locale->nplurals ) :
|
||||||
|
echo '<span class="translation-text">' . esc_translation( $translation->translations[0] ) . '</span>';
|
||||||
|
elseif ( $translation->plural && 2 === $locale->nplurals && 'n != 1' === $locale->plural_expression ) :
|
||||||
|
?>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<small>Singular:</small><br>
|
||||||
|
<?php
|
||||||
|
if ( ! isset( $translation->translations[0] ) || gp_is_empty_string( $translation->translations[0] ) ) {
|
||||||
|
echo $missing_text;
|
||||||
|
} else {
|
||||||
|
echo '<span class="translation-text">' . esc_translation( $translation->translations[0] ) . '</span>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<small>Plural:</small><br>
|
||||||
|
<?php
|
||||||
|
if ( ! isset( $translation->translations[1] ) || gp_is_empty_string( $translation->translations[1] ) ) {
|
||||||
|
echo $missing_text;
|
||||||
|
} else {
|
||||||
|
echo '<span class="translation-text">' . esc_translation( $translation->translations[1] ) . '</span>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<?php
|
||||||
|
else :
|
||||||
|
echo '<ul>';
|
||||||
|
foreach( range( 0, $locale->nplurals - 1 ) as $plural_index ):
|
||||||
|
$plural_string = implode(', ', $locale->numbers_for_index( $plural_index ) );
|
||||||
|
?>
|
||||||
|
<li>
|
||||||
|
<small class="with-tooltip" aria-label="<?php printf('This plural form is used for numbers like: %s', $plural_string ); ?>">
|
||||||
|
<?php echo $plural_string; ?>:
|
||||||
|
</small><br>
|
||||||
|
<?php
|
||||||
|
if ( ! isset( $translation->translations[ $plural_index ] ) || gp_is_empty_string( $translation->translations[ $plural_index ] ) ) {
|
||||||
|
echo $missing_text;
|
||||||
|
} else {
|
||||||
|
echo '<span class="translation-text">' . esc_translation( $translation->translations[ $plural_index ] ) . '</span>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</li>
|
||||||
|
<?php
|
||||||
|
endforeach;
|
||||||
|
echo '</ul>';
|
||||||
|
endif; ?>
|
||||||
|
</td>
|
||||||
|
<td class="actions">
|
||||||
|
<a href="#" class="action edit"><?php _e( 'Details', 'glotpress' ); ?></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
|
@ -0,0 +1,25 @@
|
||||||
|
<dl>
|
||||||
|
<dt><label for="set[locale]"><?php _e( 'Locale', 'glotpress' ); ?></label></dt>
|
||||||
|
<dd>
|
||||||
|
<?php echo gp_locales_dropdown( 'set[locale]', $set->locale ); ?>
|
||||||
|
<a href="#" id="copy"><?php _e( 'Use as name', 'glotpress' ); ?></a>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dt><label for="set[name]"><?php _e( 'Name', 'glotpress' ); ?></label></dt>
|
||||||
|
<dd><input type="text" name="set[name]" value="<?php echo esc_html( $set->name ); ?>" id="set[name]"></dd>
|
||||||
|
|
||||||
|
<!-- TODO: make slug edit WordPress style -->
|
||||||
|
<dt><label for="set[slug]"><?php _e( 'Slug', 'glotpress' ); ?></label></dt>
|
||||||
|
<dd><input type="text" name="set[slug]" value="<?php echo esc_html( $set->slug? $set->slug : 'default' ); ?>" id="set[slug]"></dd>
|
||||||
|
|
||||||
|
<dt><label for="set[project_id]"><?php _e( 'Project (ID)', 'glotpress' ); ?></label></dt>
|
||||||
|
<dd><input type="text" name="set[project_id]" value="<?php echo esc_attr( $set->project_id ); ?>" id="set[project_id]">
|
||||||
|
</dl>
|
||||||
|
<?php echo gp_js_focus_on( 'set[locale]' ) . "\n"; ?>
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery('#copy').click(function() {
|
||||||
|
var text = jQuery('#set\\[locale\\] option:selected').html().replace(/^\S+\s+\S+\s+/, '').replace(/&mdash|—/, '');
|
||||||
|
jQuery('#set\\[name\\]').val(text);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
</script>
|
12
content/plugins/f-translate/templates/translations.api.php
Normal file
12
content/plugins/f-translate/templates/translations.api.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
foreach ( $translations as $index => $translation ) {
|
||||||
|
foreach ( (array) $translation as $key => $value ) {
|
||||||
|
if ( $value instanceof WP_User ) {
|
||||||
|
$translations[ $index ]->$key = (object) array_intersect_key( (array) $value->data, array_flip( array(
|
||||||
|
'user_login',
|
||||||
|
'display_name',
|
||||||
|
) ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo wp_json_encode( $translations );
|
271
content/plugins/f-translate/wporg-gp-routes/inc/class-plugin.php
Normal file
271
content/plugins/f-translate/wporg-gp-routes/inc/class-plugin.php
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace WordPressdotorg\GlotPress\Routes;
|
||||||
|
|
||||||
|
use GP;
|
||||||
|
use GP_Locales;
|
||||||
|
use WP_CLI;
|
||||||
|
|
||||||
|
class Plugin {
|
||||||
|
|
||||||
|
public const CACHE_GROUP = 'wporg-translate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Plugin The singleton instance.
|
||||||
|
*/
|
||||||
|
private static $instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns always the same instance of this plugin.
|
||||||
|
*
|
||||||
|
* @return Plugin
|
||||||
|
*/
|
||||||
|
public static function get_instance() {
|
||||||
|
if ( ! ( self::$instance instanceof Plugin ) ) {
|
||||||
|
self::$instance = new Plugin();
|
||||||
|
}
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new Plugin object.
|
||||||
|
*/
|
||||||
|
private function __construct() {
|
||||||
|
add_action( 'plugins_loaded', [ $this, 'plugins_loaded' ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the plugin.
|
||||||
|
*/
|
||||||
|
public function plugins_loaded() {
|
||||||
|
if ( file_exists( WPORGPATH . 'wp-content/plugins/plugin-directory/class-template.php' ) ) {
|
||||||
|
include_once WPORGPATH . 'wp-content/plugins/plugin-directory/class-template.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
add_action( 'template_redirect', [ $this, 'register_routes' ], 5 );
|
||||||
|
add_filter( 'gp_locale_glossary_path_prefix', [ $this, 'set_locale_glossary_path_prefix' ] );
|
||||||
|
|
||||||
|
add_action( 'init', [ $this, 'respect_robots_txt' ], 9 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GlotPress match-all route is prepended early before a lot of rules, including the robots.txt rules.
|
||||||
|
*
|
||||||
|
* gp_rewrite_rules() is called from gp_init() on init at priority 10, we need to insert before that.
|
||||||
|
*/
|
||||||
|
function respect_robots_txt() {
|
||||||
|
// ?robots=1 is here to trigger `is_robots()`, which prevents canonical.
|
||||||
|
// ?gp_route=robots.txt is here, as GlotPress ultimately is the router for the request.
|
||||||
|
add_rewrite_rule( '^robots\.txt$', 'index.php?robots=1&gp_route=robots.txt', 'top' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the path prefix for a locale glossary to '/locale'.
|
||||||
|
*
|
||||||
|
* @return string '/locale'
|
||||||
|
*/
|
||||||
|
public function set_locale_glossary_path_prefix() {
|
||||||
|
return '/locale';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers custom routes and removes default routes.
|
||||||
|
*
|
||||||
|
* Removes:
|
||||||
|
* - API: /languages
|
||||||
|
* - API: /languages/$locale
|
||||||
|
* - /languages/$locale
|
||||||
|
* - /languages/$locale
|
||||||
|
* - /languages/$locale/$path
|
||||||
|
* - (/languages)/$locale/$dir/glossary
|
||||||
|
* - (/languages)/$locale/$dir/glossary/-new
|
||||||
|
* - (/languages)/$locale/$dir/glossary/-delete
|
||||||
|
* - (/languages)/$locale/$dir/glossary/-export
|
||||||
|
* - (/languages)/$locale/$dir/glossary/-import
|
||||||
|
* - /profile/$path
|
||||||
|
* - /projects/wp-plugins/?
|
||||||
|
* - /projects/wp-themes/?
|
||||||
|
*
|
||||||
|
* Adds:
|
||||||
|
* - /
|
||||||
|
* - /robots.txt
|
||||||
|
* - /locale/$locale
|
||||||
|
* - /locale/$locale/$path
|
||||||
|
* - /locale/$locale/$path/$path
|
||||||
|
* - /locale/$locale/$path/$path/$path
|
||||||
|
* - (/locale)/$locale/$dir/glossary
|
||||||
|
* - (/locale)/$locale/$dir/glossary/-new
|
||||||
|
* - (/locale)/$locale/$dir/glossary/-delete
|
||||||
|
* - (/locale)/$locale/$dir/glossary/-export
|
||||||
|
* - (/locale)/$locale/$dir/glossary/-import
|
||||||
|
* - /locale/$locale/$dir/stats(?:/(plugins|themes))?s
|
||||||
|
* - /stats/?
|
||||||
|
* - /projects/wp-plugins/$project
|
||||||
|
* - /projects/wp-plugins/$project/contributors
|
||||||
|
* - /projects/wp-plugins/$project/language-packs
|
||||||
|
* - /projects/wp-themes/$project
|
||||||
|
* - /projects/wp-themes/$project/contributors
|
||||||
|
* - /projects/wp-themes/$project/language-packs
|
||||||
|
*/
|
||||||
|
public function register_routes() {
|
||||||
|
$request_uri = GP::$router->request_uri();
|
||||||
|
$path = '(.+?)';
|
||||||
|
$dir = '([^_/][^/]*)';
|
||||||
|
$project = '([^/]*)/?';
|
||||||
|
|
||||||
|
$locale = '(' . implode( '|', array_map( function( $locale ) { return $locale->slug; }, GP_Locales::locales() ) ) . ')';
|
||||||
|
$sets = '(default|formal|informal|ao90|valencia|latin)';
|
||||||
|
|
||||||
|
if ( gp_startswith( $request_uri, '/' . GP::$router->api_prefix . '/' ) ) { // API requests.
|
||||||
|
// Delete default routes.
|
||||||
|
GP::$router->remove( '/languages' );
|
||||||
|
GP::$router->remove( "/languages/$locale" );
|
||||||
|
GP::$router->remove( '/profile' );
|
||||||
|
} else {
|
||||||
|
// Delete default routes.
|
||||||
|
GP::$router->remove( "/languages/$locale" );
|
||||||
|
GP::$router->remove( "/languages/$locale/$path" );
|
||||||
|
GP::$router->remove( "(/languages)/$locale/$dir/glossary" );
|
||||||
|
GP::$router->remove( "(/languages)/$locale/$dir/glossary", 'post' );
|
||||||
|
GP::$router->remove( "(/languages)/$locale/$dir/glossary/-new", 'post' );
|
||||||
|
GP::$router->remove( "(/languages)/$locale/$dir/glossary/-delete", 'post' );
|
||||||
|
GP::$router->remove( "(/languages)/$locale/$dir/glossary/-export" );
|
||||||
|
GP::$router->remove( "(/languages)/$locale/$dir/glossary/-import" );
|
||||||
|
GP::$router->remove( "(/languages)/$locale/$dir/glossary/-import", 'post' );
|
||||||
|
|
||||||
|
GP::$router->remove( '/profile' );
|
||||||
|
GP::$router->remove( "/profile/$path" );
|
||||||
|
|
||||||
|
// Redirect routes.
|
||||||
|
GP::$router->prepend( '/languages', array( __NAMESPACE__ . '\Routes\Redirector', 'redirect_languages' ) );
|
||||||
|
GP::$router->prepend( "/languages/$path", array( __NAMESPACE__ . '\Routes\Redirector', 'redirect_languages' ) );
|
||||||
|
GP::$router->prepend( '/projects/wp-plugins/?', array( __NAMESPACE__ . '\Routes\Redirector', 'redirect_index' ) );
|
||||||
|
GP::$router->prepend( '/projects/wp-themes/?', array( __NAMESPACE__ . '\Routes\Redirector', 'redirect_index' ) );
|
||||||
|
GP::$router->prepend( "/projects/wp/$path/$locale/$dir/glossary", array( __NAMESPACE__ . '\Routes\Redirector', 'redirect_old_glossary' ) );
|
||||||
|
|
||||||
|
// Register custom routes.
|
||||||
|
GP::$router->prepend( '/', array( __NAMESPACE__ . '\Routes\Index', 'get_locales' ) );
|
||||||
|
GP::$router->prepend( "/locale/$locale", array( __NAMESPACE__ . '\Routes\Locale', 'get_locale_projects' ) );
|
||||||
|
GP::$router->prepend( "/locale/$locale/$sets", array( __NAMESPACE__ . '\Routes\Locale', 'get_locale_projects' ) );
|
||||||
|
GP::$router->prepend( "/locale/$locale/$sets/$path", array( __NAMESPACE__ . '\Routes\Locale', 'get_locale_projects' ) );
|
||||||
|
GP::$router->prepend( "/locale/$locale/$sets/$path/$path", array( __NAMESPACE__ . '\Routes\Locale', 'get_locale_project' ) );
|
||||||
|
GP::$router->prepend( "(/locale)/$locale/$sets/glossary", array( 'GP_Route_Glossary_Entry', 'glossary_entries_get' ) );
|
||||||
|
GP::$router->prepend( "(/locale)/$locale/$sets/glossary", array( 'GP_Route_Glossary_Entry', 'glossary_entries_post' ), 'post' );
|
||||||
|
GP::$router->prepend( "(/locale)/$locale/$sets/glossary/-new", array( 'GP_Route_Glossary_Entry', 'glossary_entry_add_post' ), 'post' );
|
||||||
|
GP::$router->prepend( "(/locale)/$locale/$sets/glossary/-delete", array( 'GP_Route_Glossary_Entry', 'glossary_entry_delete_post' ), 'post' );
|
||||||
|
GP::$router->prepend( "(/locale)/$locale/$sets/glossary/-export", array( 'GP_Route_Glossary_Entry', 'export_glossary_entries_get' ) );
|
||||||
|
GP::$router->prepend( "(/locale)/$locale/$sets/glossary/-import", array( 'GP_Route_Glossary_Entry', 'import_glossary_entries_get' ) );
|
||||||
|
GP::$router->prepend( "(/locale)/$locale/$sets/glossary/-import", array( 'GP_Route_Glossary_Entry', 'import_glossary_entries_post' ), 'post' );
|
||||||
|
GP::$router->prepend( "/locale/$locale/$sets/stats(?:/(plugins|themes))?", array( __NAMESPACE__ . '\Routes\Stats', 'get_stats_plugin_theme_overview' ) );
|
||||||
|
GP::$router->prepend( '/stats', array( __NAMESPACE__ . '\Routes\Stats', 'get_stats_overview' ) );
|
||||||
|
GP::$router->prepend( '/consistency', array( __NAMESPACE__ . '\Routes\Consistency', 'get_search_form' ) );
|
||||||
|
|
||||||
|
// Project routes.
|
||||||
|
GP::$router->prepend( "/projects/wp-plugins/$project", array( __NAMESPACE__ . '\Routes\WP_Plugins', 'get_plugin_projects' ) );
|
||||||
|
GP::$router->prepend( "/projects/wp-plugins/$project/contributors", array( __NAMESPACE__ . '\Routes\WP_Plugins', 'get_plugin_contributors' ) );
|
||||||
|
GP::$router->prepend( "/projects/wp-plugins/$project/language-packs", array( __NAMESPACE__ . '\Routes\WP_Plugins', 'get_plugin_language_packs' ) );
|
||||||
|
GP::$router->prepend( "/projects/wp-themes/$project", array( __NAMESPACE__ . '\Routes\WP_Themes', 'get_theme_projects' ) );
|
||||||
|
GP::$router->prepend( "/projects/wp-themes/$project/contributors", array( __NAMESPACE__ . '\Routes\WP_Themes', 'get_theme_contributors' ) );
|
||||||
|
GP::$router->prepend( "/projects/wp-themes/$project/language-packs", array( __NAMESPACE__ . '\Routes\WP_Themes', 'get_theme_language_packs' ) );
|
||||||
|
|
||||||
|
// Add Robots.txt support
|
||||||
|
GP::$router->prepend( '/robots\.txt', 'do_robots' );
|
||||||
|
|
||||||
|
if ( defined( 'TRANSLATE_MAINTENANCE_ACTIVE' ) ) {
|
||||||
|
GP::$router->prepend( '.*', array( __NAMESPACE__ . '\Routes\Maintenance', 'show_maintenance_message' ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the translation status of the WordPress project per locale.
|
||||||
|
*/
|
||||||
|
public static function get_translation_status() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
if ( ! isset( $wpdb->project_translation_status ) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cached = wp_cache_get( 'translation-status', self::CACHE_GROUP );
|
||||||
|
if ( false !== $cached ) {
|
||||||
|
return $cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translation_status = $wpdb->get_results( $wpdb->prepare(
|
||||||
|
"SELECT `locale`, `all` AS `all_count`, `waiting` AS `waiting_count`, `current` AS `current_count`, `fuzzy` AS `fuzzy_count`
|
||||||
|
FROM {$wpdb->project_translation_status}
|
||||||
|
WHERE `project_id` = %d AND `locale_slug` = %s",
|
||||||
|
2, // 2 = wp/dev
|
||||||
|
'default'
|
||||||
|
), OBJECT_K );
|
||||||
|
|
||||||
|
wp_cache_set( 'translation-status', $translation_status, 'wporg-translate', 15 * MINUTE_IN_SECONDS );
|
||||||
|
|
||||||
|
return $translation_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates contributors count per locale.
|
||||||
|
*/
|
||||||
|
public static function get_contributors_count() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$cached = wp_cache_get( 'contributors-count', self::CACHE_GROUP );
|
||||||
|
if ( false !== $cached ) {
|
||||||
|
return $cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! isset( $wpdb->user_translations_count ) ) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$locales = self::get_existing_locales();
|
||||||
|
$db_counts = $wpdb->get_results(
|
||||||
|
"SELECT `locale`, COUNT( DISTINCT user_id ) as `count` FROM {$wpdb->user_translations_count} WHERE `accepted` > 0 GROUP BY `locale`",
|
||||||
|
OBJECT_K
|
||||||
|
);
|
||||||
|
|
||||||
|
$counts = array();
|
||||||
|
foreach ( $locales as $locale ) {
|
||||||
|
if ( isset( $db_counts[ $locale ] ) ) {
|
||||||
|
$counts[ $locale ] = (int) $db_counts[ $locale ]->count;
|
||||||
|
} else {
|
||||||
|
$counts[ $locale ] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_cache_set( 'contributors-count', $counts, self::CACHE_GROUP, 15 * MINUTE_IN_SECONDS );
|
||||||
|
|
||||||
|
return $counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves existing locales of the main wp/dev project.
|
||||||
|
*
|
||||||
|
* Much faster alternative to `GP::$translation_set->existing_locales();` since it avoids
|
||||||
|
* the expensive DISTINCT.
|
||||||
|
*
|
||||||
|
* @return array List of GlotPress locales.
|
||||||
|
*/
|
||||||
|
public static function get_existing_locales() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$cached = wp_cache_get( 'existing-locales', self::CACHE_GROUP );
|
||||||
|
if ( false !== $cached ) {
|
||||||
|
return $cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
$existing_locales = $wpdb->get_col(
|
||||||
|
$wpdb->prepare(
|
||||||
|
"SELECT locale FROM {$wpdb->gp_translation_sets} WHERE `project_id` = %d and slug = %s",
|
||||||
|
2, // 2 = wp/dev
|
||||||
|
'default'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
wp_cache_set( 'existing-locales', $existing_locales, self::CACHE_GROUP, HOUR_IN_SECONDS );
|
||||||
|
|
||||||
|
return $existing_locales;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,187 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace WordPressdotorg\GlotPress\Routes\Routes;
|
||||||
|
|
||||||
|
use GP;
|
||||||
|
use GP_Locales;
|
||||||
|
use GP_Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consistency Route Class.
|
||||||
|
*
|
||||||
|
* Provides the route for translate.wordpress.org/consistency.
|
||||||
|
*/
|
||||||
|
class Consistency extends GP_Route {
|
||||||
|
|
||||||
|
private $cache_group = 'wporg-translate';
|
||||||
|
|
||||||
|
const PROJECTS = array(
|
||||||
|
1 => 'WordPress',
|
||||||
|
523 => 'Themes',
|
||||||
|
17 => 'Plugins',
|
||||||
|
487 => 'Meta',
|
||||||
|
281 => 'Apps',
|
||||||
|
473698 => 'Patterns',
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints a search form and the search results for a consistency view.
|
||||||
|
*/
|
||||||
|
public function get_search_form() {
|
||||||
|
$sets = $this->get_translation_sets();
|
||||||
|
|
||||||
|
$search = $set = $project = '';
|
||||||
|
$search_case_sensitive = false;
|
||||||
|
|
||||||
|
if ( isset( $_REQUEST['search'] ) && strlen( $_REQUEST['search'] ) ) {
|
||||||
|
$search = wp_unslash( $_REQUEST['search'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! empty( $_REQUEST['set'] ) ) {
|
||||||
|
$set = wp_unslash( $_REQUEST['set'] );
|
||||||
|
if ( ! isset( $sets[ $set ] ) ) {
|
||||||
|
$set = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! empty( $_REQUEST['search_case_sensitive'] ) ) {
|
||||||
|
$search_case_sensitive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! empty( $_REQUEST['project'] ) && isset( self::PROJECTS[ $_REQUEST['project'] ] ) ) {
|
||||||
|
$project = $_REQUEST['project'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$locale = '';
|
||||||
|
$set_slug = '';
|
||||||
|
$locale_is_rtl = false;
|
||||||
|
|
||||||
|
if ( $set ) {
|
||||||
|
list( $locale, $set_slug ) = explode( '/', $set );
|
||||||
|
$locale_is_rtl = 'rtl' === GP_Locales::by_slug( $locale )->text_direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
$performed_search = false;
|
||||||
|
if ( strlen( $search ) && $locale && $set_slug ) {
|
||||||
|
$performed_search = true;
|
||||||
|
$results = $this->query( [
|
||||||
|
'search' => $search,
|
||||||
|
'locale' => $locale,
|
||||||
|
'set_slug' => $set_slug,
|
||||||
|
'case_sensitive' => $search_case_sensitive,
|
||||||
|
'project' => $project,
|
||||||
|
] );
|
||||||
|
|
||||||
|
$translations = wp_list_pluck( $results, 'translation', 'translation_id' );
|
||||||
|
$translations_unique = array_values( array_unique( $translations ) );
|
||||||
|
$translations_unique_counts = array_count_values( $translations );
|
||||||
|
|
||||||
|
// Sort the unique translations by highest count first.
|
||||||
|
arsort( $translations_unique_counts );
|
||||||
|
}
|
||||||
|
|
||||||
|
$projects = self::PROJECTS;
|
||||||
|
|
||||||
|
$this->tmpl( 'consistency', get_defined_vars() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of unique translation sets.
|
||||||
|
*
|
||||||
|
* @return array Array of sets.
|
||||||
|
*/
|
||||||
|
private function get_translation_sets() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$sets = wp_cache_get( 'translation-sets', $this->cache_group );
|
||||||
|
|
||||||
|
if ( empty( $sets ) ) {
|
||||||
|
$_sets = $wpdb->get_results( "SELECT name, locale, slug FROM {$wpdb->gp_translation_sets} GROUP BY locale, slug ORDER BY name" );
|
||||||
|
|
||||||
|
$sets = array();
|
||||||
|
foreach ( $_sets as $set ) {
|
||||||
|
$sets[ "{$set->locale}/$set->slug" ] = $set->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_cache_set( 'translation-sets', $sets, $this->cache_group, DAY_IN_SECONDS );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the search query.
|
||||||
|
*
|
||||||
|
* @param array $args Query arguments.
|
||||||
|
*
|
||||||
|
* @return array The search results.
|
||||||
|
*/
|
||||||
|
private function query( $args ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
if ( $args['case_sensitive'] ) {
|
||||||
|
$collation = 'BINARY';
|
||||||
|
} else {
|
||||||
|
$collation = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$search = $wpdb->prepare( "= {$collation} %s", $args['search'] );
|
||||||
|
$locale = $wpdb->prepare( '%s', $args['locale'] );
|
||||||
|
$set_slug = $wpdb->prepare( '%s', $args['set_slug'] );
|
||||||
|
|
||||||
|
$project_where = '';
|
||||||
|
if ( $args['project'] ) {
|
||||||
|
$project = GP::$project->get( $args['project'] );
|
||||||
|
$project_where = $wpdb->prepare( 'AND p.path LIKE %s', $wpdb->esc_like( $project->path ) . '/%' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$results = $wpdb->get_results( "
|
||||||
|
SELECT
|
||||||
|
p.name AS project_name,
|
||||||
|
p.id AS project_id,
|
||||||
|
p.path AS project_path,
|
||||||
|
p.parent_project_id AS project_parent_id,
|
||||||
|
p.active AS active,
|
||||||
|
o.singular AS original_singular,
|
||||||
|
o.plural AS original_plural,
|
||||||
|
o.context AS original_context,
|
||||||
|
o.id AS original_id,
|
||||||
|
t.translation_0 AS translation,
|
||||||
|
t.date_added AS translation_added,
|
||||||
|
t.id AS translation_id
|
||||||
|
FROM {$wpdb->gp_originals} AS o
|
||||||
|
JOIN
|
||||||
|
{$wpdb->gp_projects} AS p ON p.id = o.project_id
|
||||||
|
JOIN
|
||||||
|
{$wpdb->gp_translations} AS t ON o.id = t.original_id
|
||||||
|
JOIN
|
||||||
|
{$wpdb->gp_translation_sets} as ts on ts.id = t.translation_set_id
|
||||||
|
WHERE
|
||||||
|
p.active = 1
|
||||||
|
AND t.status = 'current'
|
||||||
|
AND o.status = '+active' AND o.singular {$search}
|
||||||
|
AND ts.locale = {$locale} AND ts.slug = {$set_slug}
|
||||||
|
{$project_where}
|
||||||
|
LIMIT 0, 500
|
||||||
|
" );
|
||||||
|
|
||||||
|
if ( ! $results ) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group by translation and project path. Done in PHP because it's faster as in MySQL.
|
||||||
|
usort( $results, [ $this, '_sort_callback' ] );
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function _sort_callback( $a, $b ) {
|
||||||
|
$sort = strnatcmp( $a->translation . $a->original_context, $b->translation . $b->original_context );
|
||||||
|
if ( 0 === $sort ) {
|
||||||
|
$sort = strnatcmp( $a->project_path, $b->project_path );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sort;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace WordPressdotorg\GlotPress\Routes\Routes;
|
||||||
|
|
||||||
|
use GP_Locales;
|
||||||
|
use GP_Route;
|
||||||
|
use WordPressdotorg\GlotPress\Routes\Plugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index Route Class.
|
||||||
|
*
|
||||||
|
* Provides the route for translate.wordpress.org/.
|
||||||
|
*/
|
||||||
|
class Index extends GP_Route {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints all exisiting locales as cards.
|
||||||
|
*/
|
||||||
|
public function get_locales() {
|
||||||
|
$existing_locales = Plugin::get_existing_locales();
|
||||||
|
|
||||||
|
$locales = array();
|
||||||
|
foreach ( $existing_locales as $locale ) {
|
||||||
|
$locales[] = GP_Locales::by_slug( $locale );
|
||||||
|
}
|
||||||
|
usort( $locales, array( $this, '_sort_english_name_callback' ) );
|
||||||
|
unset( $existing_locales );
|
||||||
|
|
||||||
|
$contributors_count = Plugin::get_contributors_count();
|
||||||
|
$translation_status = Plugin::get_translation_status();
|
||||||
|
|
||||||
|
$this->tmpl( 'index-locales', get_defined_vars() );
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _sort_english_name_callback( $a, $b ) {
|
||||||
|
return $a->english_name <=> $b->english_name;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace WordPressdotorg\GlotPress\Routes\Routes;
|
||||||
|
|
||||||
|
use GP_Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maintenance Route Class.
|
||||||
|
*/
|
||||||
|
class Maintenance extends GP_Route {
|
||||||
|
|
||||||
|
public function show_maintenance_message() {
|
||||||
|
wp_die( 'Briefly unavailable for scheduled maintenance. Check back in ~30 minutes.', 'Maintenance', [ 'response' => 503 ] );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace WordPressdotorg\GlotPress\Routes\Routes;
|
||||||
|
|
||||||
|
use GP;
|
||||||
|
use GP_Route;
|
||||||
|
use GP_Route_Glossary_Entry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirector Route Class.
|
||||||
|
*
|
||||||
|
* Provides redirection routes.
|
||||||
|
*/
|
||||||
|
class Redirector extends GP_Route {
|
||||||
|
|
||||||
|
public function redirect_languages( $path = '' ) {
|
||||||
|
if ( empty( $path ) ) {
|
||||||
|
$this->redirect( '/' );
|
||||||
|
} else {
|
||||||
|
$this->redirect( "/locale/$path" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function redirect_index() {
|
||||||
|
$this->redirect( '/' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirects previous glossary paths from `/projects/wp/dev/$locale/$slug/glossary`
|
||||||
|
* to `/locale/$locale/$slug/glossary`.
|
||||||
|
*
|
||||||
|
* @param string $project_path Path of a project.
|
||||||
|
* @param string $locale Locale slug.
|
||||||
|
* @param string $slug Slug if a translation set.
|
||||||
|
*/
|
||||||
|
public function redirect_old_glossary( $project_path = '', $locale = '', $slug = '' ) {
|
||||||
|
$project_path = "wp/$project_path";
|
||||||
|
|
||||||
|
$project = GP::$project->by_path( $project_path );
|
||||||
|
if ( ! $project ) {
|
||||||
|
$route_glossary_entry = new GP_Route_Glossary_Entry();
|
||||||
|
return $route_glossary_entry->glossary_entries_get( $project_path, $locale, $slug );
|
||||||
|
}
|
||||||
|
|
||||||
|
$translation_set = GP::$translation_set->by_project_id_slug_and_locale( $project->id, $slug, $locale );
|
||||||
|
if ( ! $translation_set ) {
|
||||||
|
$route_glossary_entry = new GP_Route_Glossary_Entry();
|
||||||
|
return $route_glossary_entry->glossary_entries_get( $project_path, $locale, $slug );
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the current project has a glossary stop here and load it.
|
||||||
|
$glossary = GP::$glossary->by_set_id( $translation_set->id );
|
||||||
|
if ( $glossary ) {
|
||||||
|
$route_glossary_entry = new GP_Route_Glossary_Entry();
|
||||||
|
return $route_glossary_entry->glossary_entries_get( $project_path, $locale, $slug );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise redirect to the locale glossary.
|
||||||
|
$locale_glossary_translation_set = GP::$translation_set->by_project_id_slug_and_locale( 0, $slug, $locale );
|
||||||
|
$locale_glossary = GP::$glossary->by_set_id( $locale_glossary_translation_set->id );
|
||||||
|
$this->redirect( $locale_glossary->path() );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,247 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace WordPressdotorg\GlotPress\Routes\Routes;
|
||||||
|
|
||||||
|
use GP;
|
||||||
|
use GP_Route;
|
||||||
|
use GP_Locales;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stats Route Class.
|
||||||
|
*
|
||||||
|
* Provides the route for translate.wordpress.org/stats.
|
||||||
|
*/
|
||||||
|
class Stats extends GP_Route {
|
||||||
|
|
||||||
|
public function get_stats_overview() {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$projects = array(
|
||||||
|
'patterns/core' => false,
|
||||||
|
'meta/wordpress-org' => false,
|
||||||
|
'meta/rosetta' => false,
|
||||||
|
'meta/browsehappy' => false,
|
||||||
|
'meta/themes' => false,
|
||||||
|
'meta/plugins-v3' => false,
|
||||||
|
'meta/forums' => false,
|
||||||
|
'apps/android' => false,
|
||||||
|
'apps/ios' => false,
|
||||||
|
'waiting' => false,
|
||||||
|
'wp-themes' => false,
|
||||||
|
'wp-plugins' => false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// I'm sure there's somewhere to fetch these from statically defined
|
||||||
|
$wp_project = GP::$project->by_path( 'wp' );
|
||||||
|
$previous_wp_version = WP_CORE_STABLE_BRANCH - 0.1;
|
||||||
|
foreach ( GP::$project->find_many( array( 'parent_project_id' => $wp_project->id, 'active' => 1 ), 'name ASC' ) as $wp_sub_project ) {
|
||||||
|
if ( 'dev' === $wp_sub_project->slug || version_compare( $previous_wp_version, (float) $wp_sub_project->name, '<=' ) ) {
|
||||||
|
// Prefix the WordPress projects...
|
||||||
|
$wp_sub_project->name = $wp_project->name . ' ' . $wp_sub_project->name;
|
||||||
|
$projects = array_merge( array( $wp_sub_project->path => $wp_sub_project ), $projects );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the projects for each display item
|
||||||
|
array_walk( $projects, function( &$project, $project_slug ) {
|
||||||
|
if ( ! $project ) {
|
||||||
|
$project = GP::$project->by_path( $project_slug );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
$all_project_paths_sql = '"' . implode( '", "', array_keys( $projects ) ) . '"';
|
||||||
|
$sql = "SELECT
|
||||||
|
path, locale, locale_slug,
|
||||||
|
(100 * stats.current/stats.all) as percent_complete,
|
||||||
|
stats.waiting+stats.fuzzy as waiting_strings,
|
||||||
|
stats.date_modified as last_modified
|
||||||
|
FROM {$wpdb->project_translation_status} stats
|
||||||
|
LEFT JOIN {$wpdb->gp_projects} p ON stats.project_id = p.id
|
||||||
|
WHERE
|
||||||
|
p.path IN ( $all_project_paths_sql )
|
||||||
|
AND p.active = 1";
|
||||||
|
|
||||||
|
$rows = $wpdb->get_results( $sql );
|
||||||
|
|
||||||
|
// Split out into $[Locale][Project] = %
|
||||||
|
$translation_locale_statuses = array();
|
||||||
|
foreach ( $rows as $set ) {
|
||||||
|
$locale_key = $set->locale;
|
||||||
|
if ( 'default' !== $set->locale_slug ) {
|
||||||
|
$locale_key = $set->locale . '/' . $set->locale_slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
$gp_locale = GP_Locales::by_slug( $locale_key );
|
||||||
|
if ( ! $gp_locale || ! $gp_locale->wp_locale ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* > 50% round down, so that a project with all strings except 1 translated shows 99%, instead of 100%.
|
||||||
|
* < 50% round up, so that a project with just a few strings shows 1%, instead of 0%.
|
||||||
|
*/
|
||||||
|
$percent_complete = (float) $set->percent_complete;
|
||||||
|
$percent_complete = ( $percent_complete > 50 ) ? floor( $percent_complete ) : ceil( $percent_complete );
|
||||||
|
$translation_locale_statuses[ $locale_key ][ $set->path ] = $percent_complete;
|
||||||
|
|
||||||
|
// Don't include these in the 'waiting' section, override the value to be waiting strings w/ Date Modified.
|
||||||
|
if ( 'wp-plugins' === $set->path || 'wp-themes' === $set->path ) {
|
||||||
|
$translation_locale_statuses[ $locale_key ][ $set->path ] = $set->waiting_strings;
|
||||||
|
$projects[ $set->path ]->cache_last_updated = $set->last_modified;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! isset( $translation_locale_statuses[ $locale_key ]['waiting'] ) ) {
|
||||||
|
$translation_locale_statuses[ $locale_key ]['waiting'] = 0;
|
||||||
|
}
|
||||||
|
$translation_locale_statuses[ $locale_key ]['waiting'] += (int) $set->waiting_strings;
|
||||||
|
}
|
||||||
|
unset( $rows, $locale_key, $set );
|
||||||
|
|
||||||
|
// Calculate a list of [Locale] = % subtotals
|
||||||
|
$translation_locale_complete = array();
|
||||||
|
foreach ( $translation_locale_statuses as $locale => $sets ) {
|
||||||
|
unset( $sets['waiting'], $sets['wp-plugins'], $sets['wp-themes'] );
|
||||||
|
$sets_count = count( $sets );
|
||||||
|
if ( $sets_count ) {
|
||||||
|
$translation_locale_complete[ $locale ] = round( array_sum( $sets ) / $sets_count, 3 );
|
||||||
|
} else {
|
||||||
|
$translation_locale_complete[ $locale ] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset( $locale, $sets );
|
||||||
|
|
||||||
|
// Sort by translation completeness, least number of waiting strings, and locale slug.
|
||||||
|
uksort( $translation_locale_complete, function ( $a, $b ) use ( $translation_locale_complete, $translation_locale_statuses ) {
|
||||||
|
if ( $translation_locale_complete[ $a ] < $translation_locale_complete[ $b ] ) {
|
||||||
|
return 1;
|
||||||
|
} elseif ( $translation_locale_complete[ $a ] == $translation_locale_complete[ $b ] ) {
|
||||||
|
if ( $translation_locale_statuses[ $a ]['waiting'] != $translation_locale_statuses[ $b ]['waiting'] ) {
|
||||||
|
return strnatcmp( $translation_locale_statuses[ $a ]['waiting'], $translation_locale_statuses[ $b ]['waiting'] );
|
||||||
|
} else {
|
||||||
|
return strnatcmp( $a, $b );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
$this->tmpl( 'stats-overview', get_defined_vars() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_stats_plugin_theme_overview( $locale, $locale_slug, $view = false ) {
|
||||||
|
global $wpdb;
|
||||||
|
if ( ! $locale || ! $locale_slug || ! $view ) {
|
||||||
|
wp_redirect( '/stats', 301 );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( 'default' !== $locale_slug ) {
|
||||||
|
$gp_locale = GP_Locales::by_slug( $locale . '/' . $locale_slug );
|
||||||
|
} else {
|
||||||
|
$gp_locale = GP_Locales::by_slug( $locale );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $gp_locale ) {
|
||||||
|
wp_redirect( '/stats', 301 );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$locale_path = $locale . '/' . $locale_slug;
|
||||||
|
|
||||||
|
$items = array();
|
||||||
|
if ( 'plugins' == $view ) {
|
||||||
|
// Fetch top ~500 plugins..
|
||||||
|
$items = get_transient( __METHOD__ . '_plugin_items' );
|
||||||
|
if ( false === $items ) {
|
||||||
|
$items = array();
|
||||||
|
foreach ( range( 1, 2 ) as $page ) { // 2 pages x 250 items per page.
|
||||||
|
$api = wp_safe_remote_get( 'https://api.wordpress.org/plugins/info/1.2/?action=query_plugins&request[per_page]=250&request[page]=' . $page );
|
||||||
|
foreach ( json_decode( wp_remote_retrieve_body( $api ) )->plugins as $plugin ) {
|
||||||
|
$items[ $plugin->slug ] = (object) [
|
||||||
|
'installs' => $plugin->active_installs,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set_transient( __METHOD__ . '_plugin_items', $items, $items ? DAY_IN_SECONDS : 5 * MINUTE_IN_SECONDS );
|
||||||
|
}
|
||||||
|
} elseif ( 'themes' == $view && defined( 'WPORG_THEME_DIRECTORY_BLOGID' ) ) {
|
||||||
|
$items = get_transient( __METHOD__ . '_theme_items' );
|
||||||
|
if ( false === $items ) {
|
||||||
|
// The Themes API API isn't playing nice.. Easiest way..
|
||||||
|
switch_to_blog( WPORG_THEME_DIRECTORY_BLOGID );
|
||||||
|
$items = $wpdb->get_results(
|
||||||
|
"SELECT p.post_name as slug, pm.meta_value as installs
|
||||||
|
FROM {$wpdb->posts} p
|
||||||
|
LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_active_installs'
|
||||||
|
WHERE p.post_type = 'repopackage' AND p.post_status = 'publish'
|
||||||
|
ORDER BY pm.meta_value+0 DESC, p.post_date
|
||||||
|
LIMIT 500",
|
||||||
|
OBJECT_K
|
||||||
|
);
|
||||||
|
restore_current_blog();
|
||||||
|
foreach ( $items as $slug => $details ) {
|
||||||
|
unset( $items[$slug]->slug );
|
||||||
|
}
|
||||||
|
set_transient( __METHOD__ . '_theme_items', $items, $items ? DAY_IN_SECONDS : 5 * MINUTE_IN_SECONDS );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wp_safe_redirect( '/stats' );
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
$project_ids = [];
|
||||||
|
foreach ( $items as $slug => $details ) {
|
||||||
|
$items[$slug]->project = GP::$project->by_path( 'wp-' . $view . '/' . $slug );
|
||||||
|
if ( ! $items[$slug]->project || ! $items[$slug]->project->active ) {
|
||||||
|
// Not all top-all-time plugins/themes have translation projects.
|
||||||
|
unset( $items[ $slug ] );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( 'plugins' == $view ) {
|
||||||
|
// For plugins, we're ignoring the Readme projects as they're not immediately useful.
|
||||||
|
// The display will use the "Code Stable" if present, else "Code Development"
|
||||||
|
$code_project =
|
||||||
|
GP::$project->by_path( 'wp-' . $view . '/' . $slug . '/stable' ) ?:
|
||||||
|
GP::$project->by_path( 'wp-' . $view . '/' . $slug . '/dev' );
|
||||||
|
|
||||||
|
// No code project? This happens when the plugin isn't translatable. The readme projects still exist though.
|
||||||
|
if ( ! $code_project ) {
|
||||||
|
unset( $items[ $slug ] );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$project_ids[ $code_project->id ] = $slug;
|
||||||
|
} else {
|
||||||
|
$project_ids[ $items[$slug]->project->id ] = $slug;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql_project_ids = implode( ', ', array_map( 'intval', array_keys( $project_ids ) ) );
|
||||||
|
$stats = $wpdb->get_results( $wpdb->prepare(
|
||||||
|
"SELECT `project_id`, `all`, `current`, `waiting`, `fuzzy`, `untranslated`
|
||||||
|
FROM {$wpdb->project_translation_status}
|
||||||
|
WHERE project_id IN ($sql_project_ids)
|
||||||
|
AND locale = %s AND locale_slug = %s",
|
||||||
|
$locale, $locale_slug
|
||||||
|
) );
|
||||||
|
|
||||||
|
// Link Projects & Stats
|
||||||
|
foreach ( $stats as $row ) {
|
||||||
|
$items[ $project_ids[ $row->project_id ] ]->stats = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final sanity check that we have data to display.
|
||||||
|
foreach ( $items as $slug => $details ) {
|
||||||
|
if (
|
||||||
|
! isset( $details->stats ) || // Didn't find any cached data for a project
|
||||||
|
! $details->stats->all // Project has no strings, not useful for this interface
|
||||||
|
) {
|
||||||
|
unset( $items[ $slug ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->tmpl( 'stats-plugin-themes-overview', compact( 'locale_path', 'view', 'gp_locale', 'items' ) );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,248 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace WordPressdotorg\GlotPress\Routes\Routes;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use DatePeriod;
|
||||||
|
use DateTime;
|
||||||
|
use GP_Route;
|
||||||
|
|
||||||
|
class WP_Directory extends GP_Route {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves stats about contributors of a specific project.
|
||||||
|
*
|
||||||
|
* @param GP_Project $project The project.
|
||||||
|
* @return array|false False if project not found, otherwise array with contributors.
|
||||||
|
*/
|
||||||
|
public function get_contributors( $project ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$contributors_by_locale = array();
|
||||||
|
$default_value = array(
|
||||||
|
'count' => 0,
|
||||||
|
'editors' => array(),
|
||||||
|
'contributors' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$translation_editors = $wpdb->get_results( $wpdb->prepare( "
|
||||||
|
SELECT
|
||||||
|
`user_id`, `locale`
|
||||||
|
FROM {$wpdb->wporg_translation_editors}
|
||||||
|
WHERE `project_id` = %d AND `locale` != 'all-locales'
|
||||||
|
", $project->id ), OBJECT );
|
||||||
|
|
||||||
|
foreach ( $translation_editors as $translation_editor ) {
|
||||||
|
if ( ! isset( $contributors_by_locale[ $translation_editor->locale ] ) ) {
|
||||||
|
$contributors_by_locale[ $translation_editor->locale ] = $default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = get_user_by( 'id', $translation_editor->user_id );
|
||||||
|
if ( ! $user ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contributors_by_locale[ $translation_editor->locale ]['editors'][ $translation_editor->user_id ] = (object) array(
|
||||||
|
'nicename' => $user->user_nicename,
|
||||||
|
'display_name' => $this->_encode( $user->display_name ),
|
||||||
|
);
|
||||||
|
|
||||||
|
$contributors_by_locale[ $translation_editor->locale ]['count']++;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset( $translation_editors );
|
||||||
|
|
||||||
|
foreach ( $this->get_translation_contributors_by_locale( $project->id ) as $row ) {
|
||||||
|
if ( ! isset( $contributors_by_locale[ $row->locale ] ) ) {
|
||||||
|
$contributors_by_locale[ $row->locale ] = $default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $contributors_by_locale[ $row->locale ]['editors'][ $row->user_id ] ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $contributors_by_locale[ $row->locale ]['contributors'][ $row->user_id ] ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = get_user_by( 'id', $row->user_id );
|
||||||
|
if ( ! $user ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contributors_by_locale[ $row->locale ]['contributors'][ $row->user_id ] = (object) array(
|
||||||
|
'nicename' => $user->user_nicename,
|
||||||
|
'display_name' => $this->_encode( $user->display_name ),
|
||||||
|
);
|
||||||
|
|
||||||
|
$contributors_by_locale[ $row->locale ]['count']++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sub_projects = $wpdb->get_col( $wpdb->prepare( "
|
||||||
|
SELECT id
|
||||||
|
FROM {$wpdb->gp_projects}
|
||||||
|
WHERE parent_project_id = %d
|
||||||
|
", $project->id ) );
|
||||||
|
|
||||||
|
foreach ( $sub_projects as $sub_project ) {
|
||||||
|
foreach ( $this->get_translation_contributors_by_locale( $sub_project ) as $row ) {
|
||||||
|
if ( ! isset( $contributors_by_locale[ $row->locale ] ) ) {
|
||||||
|
$contributors_by_locale[ $row->locale ] = $default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $contributors_by_locale[ $row->locale ]['editors'][ $row->user_id ] ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset( $contributors_by_locale[ $row->locale ]['contributors'][ $row->user_id ] ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = get_user_by( 'id', $row->user_id );
|
||||||
|
if ( ! $user ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contributors_by_locale[ $row->locale ]['contributors'][ $row->user_id ] = (object) array(
|
||||||
|
'nicename' => $user->user_nicename,
|
||||||
|
'display_name' => $this->_encode( $user->display_name ),
|
||||||
|
);
|
||||||
|
|
||||||
|
$contributors_by_locale[ $row->locale ]['count']++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $contributors_by_locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the chart data for contributors activity.
|
||||||
|
*
|
||||||
|
* @param \GP_Project $project The project.
|
||||||
|
* @return array The data to build a chart via Chartist.js.
|
||||||
|
*/
|
||||||
|
protected function get_contributors_chart_data( $project ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$sub_projects = $wpdb->get_col( $wpdb->prepare( "
|
||||||
|
SELECT id
|
||||||
|
FROM {$wpdb->gp_projects}
|
||||||
|
WHERE parent_project_id = %d
|
||||||
|
", $project->id ) );
|
||||||
|
|
||||||
|
$project_ids = array_merge( array( $project->id ), $sub_projects );
|
||||||
|
$translation_set_ids = $wpdb->get_col( "
|
||||||
|
SELECT `id` FROM {$wpdb->gp_translation_sets} WHERE `project_id` IN ( " . implode( ',', $project_ids ) . ")
|
||||||
|
" );
|
||||||
|
|
||||||
|
if ( ! $translation_set_ids ) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$date_begin = new DateTime( '-6 day' );
|
||||||
|
$date_end = new DateTime( 'NOW' );
|
||||||
|
$date_interval = new DateInterval( 'P1D' );
|
||||||
|
$date_range = new DatePeriod( $date_begin, $date_interval, $date_end );
|
||||||
|
|
||||||
|
$days = array();
|
||||||
|
foreach ( $date_range as $date ) {
|
||||||
|
$days[] = $date->format( 'Y-m-d' );
|
||||||
|
}
|
||||||
|
$days[] = $date_end->format( 'Y-m-d' );
|
||||||
|
|
||||||
|
$counts = $wpdb->get_results( "
|
||||||
|
SELECT
|
||||||
|
DATE(date_modified) AS `day`, COUNT(*) AS `count`, `status`
|
||||||
|
FROM {$wpdb->gp_translations}
|
||||||
|
WHERE
|
||||||
|
`translation_set_id` IN (" . implode( ',', $translation_set_ids ) . ")
|
||||||
|
AND date_modified >= ( CURDATE() - INTERVAL 7 DAY )
|
||||||
|
GROUP BY `status`, `day`
|
||||||
|
ORDER BY `day` DESC
|
||||||
|
" );
|
||||||
|
|
||||||
|
$status = array( 'current', 'waiting', 'rejected' );
|
||||||
|
$data = [];
|
||||||
|
foreach ( $days as $day ) {
|
||||||
|
$data[ $day ] = array_fill_keys( $status, 0 );
|
||||||
|
foreach ( $counts as $count ) {
|
||||||
|
if ( $count->day !== $day || ! in_array( $count->status, $status ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data[ $day ][ $count->status ] = (int) $count->count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$labels = array_keys( $data );
|
||||||
|
array_pop( $labels );
|
||||||
|
$labels[] = ''; // Don't show a label for today
|
||||||
|
|
||||||
|
$series = array();
|
||||||
|
$series_data = array_values( $data );
|
||||||
|
foreach ( $status as $stati ) {
|
||||||
|
$series[] = (object) array(
|
||||||
|
'name' => $stati,
|
||||||
|
'data' => wp_list_pluck( $series_data, $stati ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$chart_data = compact( 'labels', 'series' );
|
||||||
|
|
||||||
|
return $chart_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints stats about language packs of a specific project.
|
||||||
|
*
|
||||||
|
* @param string $type Type of the language pack, plugin or theme.
|
||||||
|
* @param string $slug Slug of a project.
|
||||||
|
*/
|
||||||
|
public function get_language_packs( $type, $slug ) {
|
||||||
|
if ( 'plugin' === $type ) {
|
||||||
|
$type = 'plugins';
|
||||||
|
} else {
|
||||||
|
$type = 'themes';
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = wp_remote_retrieve_body(
|
||||||
|
wp_safe_remote_get(
|
||||||
|
"https://api.wordpress.org/translations/$type/1.0/?slug={$slug}",
|
||||||
|
array(
|
||||||
|
'user-agent' => 'WordPress.org Translate',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$language_packs = $json && '{' == $json[0] ? json_decode( $json ) : null;
|
||||||
|
|
||||||
|
return $language_packs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves translators of a specific project.
|
||||||
|
*
|
||||||
|
* @param int $project_id Project ID.
|
||||||
|
* @return object Translators of the project.
|
||||||
|
*/
|
||||||
|
private function get_translation_contributors_by_locale( $project_id ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$sql = $wpdb->prepare( "
|
||||||
|
SELECT ts.`locale`, ts.`slug` AS `locale_slug`, t.`user_id`
|
||||||
|
FROM `{$wpdb->gp_translations}` t, `{$wpdb->gp_translation_sets}` ts
|
||||||
|
WHERE t.`translation_set_id` = ts.`id`
|
||||||
|
AND t.`user_id` IS NOT NULL AND t.`user_id` != 0
|
||||||
|
AND t.`date_modified` > %s
|
||||||
|
AND ts.`project_id` = %d
|
||||||
|
AND t.`status` <> 'rejected'
|
||||||
|
GROUP BY ts.`locale`, ts.`slug`, t.`user_id`
|
||||||
|
", date( 'Y-m-d', time() - YEAR_IN_SECONDS ), $project_id );
|
||||||
|
|
||||||
|
return $wpdb->get_results( $sql );
|
||||||
|
}
|
||||||
|
|
||||||
|
private function _encode( $raw ) {
|
||||||
|
$raw = mb_convert_encoding( $raw, 'UTF-8', 'ASCII, JIS, UTF-8, Windows-1252, ISO-8859-1' );
|
||||||
|
return ent2ncr( htmlspecialchars_decode( htmlentities( $raw, ENT_NOQUOTES, 'UTF-8' ), ENT_NOQUOTES ) );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,192 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace WordPressdotorg\GlotPress\Routes\Routes;
|
||||||
|
|
||||||
|
use GP;
|
||||||
|
|
||||||
|
class WP_Plugins extends WP_Directory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints stats about sub-project of a specific project.
|
||||||
|
*
|
||||||
|
* @param string $project_slug Slug of a project.
|
||||||
|
*/
|
||||||
|
public function get_plugin_projects( $project_slug ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$project_path = 'wp-plugins/' . $project_slug;
|
||||||
|
$project = GP::$project->by_path( $project_path );
|
||||||
|
if ( ! $project ) {
|
||||||
|
return $this->die_with_404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $wpdb->get_results( "
|
||||||
|
SELECT
|
||||||
|
path, locale, locale_slug,
|
||||||
|
(100 * stats.current/stats.all) as percent_complete,
|
||||||
|
stats.waiting+stats.fuzzy as waiting_strings,
|
||||||
|
stats.untranslated as untranslated
|
||||||
|
FROM {$wpdb->project_translation_status} stats
|
||||||
|
LEFT JOIN {$wpdb->gp_projects} p ON stats.project_id = p.id
|
||||||
|
WHERE
|
||||||
|
p.parent_project_id = '{$project->id}'
|
||||||
|
" );
|
||||||
|
|
||||||
|
// Split out into $[Locale][Project] = %
|
||||||
|
$translation_locale_statuses = array();
|
||||||
|
$sub_projects = array();
|
||||||
|
foreach ( $rows as $set ) {
|
||||||
|
|
||||||
|
// Find unique locale key.
|
||||||
|
$locale_key = $set->locale;
|
||||||
|
if ( 'default' != $set->locale_slug ) {
|
||||||
|
$locale_key = $set->locale . '/' . $set->locale_slug;
|
||||||
|
}
|
||||||
|
$sub_project = str_replace( "$project_path/", '', $set->path );
|
||||||
|
$sub_projects[ $sub_project ] = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* > 50% round down, so that a project with all strings except 1 translated shows 99%, instead of 100%.
|
||||||
|
* < 50% round up, so that a project with just a few strings shows 1%, instead of 0%.
|
||||||
|
*/
|
||||||
|
$percent_complete = (float) $set->percent_complete;
|
||||||
|
$translation_locale_statuses[ $locale_key ][ $sub_project ] = ( $percent_complete > 50 ) ? floor( $percent_complete ) : ceil( $percent_complete );
|
||||||
|
|
||||||
|
// Increment the amount of waiting and untranslated strings.
|
||||||
|
if ( ! isset( $translation_locale_statuses[ $locale_key ]['waiting'] ) ) {
|
||||||
|
$translation_locale_statuses[ $locale_key ]['waiting'] = 0;
|
||||||
|
}
|
||||||
|
if ( ! isset( $translation_locale_statuses[ $locale_key ]['untranslated'] ) ) {
|
||||||
|
$translation_locale_statuses[ $locale_key ]['untranslated'] = 0;
|
||||||
|
}
|
||||||
|
$translation_locale_statuses[ $locale_key ]['waiting'] += (int) $set->waiting_strings;
|
||||||
|
$translation_locale_statuses[ $locale_key ]['untranslated'] += (int) $set->untranslated;
|
||||||
|
|
||||||
|
|
||||||
|
ksort( $translation_locale_statuses[ $locale_key ], SORT_NATURAL );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the plugin has at least one code project. These won't be created if a plugin
|
||||||
|
// has no text domain defined.
|
||||||
|
$sub_projects = array_keys( $sub_projects );
|
||||||
|
$has_error = ( ! in_array( 'dev', $sub_projects ) && ! in_array( 'stable', $sub_projects ) );
|
||||||
|
|
||||||
|
unset( $project_path, $locale_key, $rows, $set, $sub_project, $sub_projects );
|
||||||
|
|
||||||
|
// Calculate a list of [Locale] = % subtotals
|
||||||
|
$translation_locale_complete = array();
|
||||||
|
foreach ( $translation_locale_statuses as $locale => $sets ) {
|
||||||
|
unset( $sets['waiting'], $sets['untranslated'] );
|
||||||
|
$translation_locale_complete[ $locale ] = round( array_sum( $sets ) / count( $sets ), 3 );
|
||||||
|
}
|
||||||
|
unset( $locale, $sets );
|
||||||
|
|
||||||
|
|
||||||
|
// Sort by translation completeness, least number of waiting strings, and locale slug.
|
||||||
|
uksort( $translation_locale_complete, function ( $a, $b ) use ( $translation_locale_complete, $translation_locale_statuses ) {
|
||||||
|
if ( $translation_locale_complete[ $a ] < $translation_locale_complete[ $b ] ) {
|
||||||
|
return 1;
|
||||||
|
} elseif ( $translation_locale_complete[ $a ] == $translation_locale_complete[ $b ] ) {
|
||||||
|
if ( $translation_locale_statuses[ $a ]['waiting'] != $translation_locale_statuses[ $b ]['waiting'] ) {
|
||||||
|
return strnatcmp( $translation_locale_statuses[ $a ]['waiting'], $translation_locale_statuses[ $b ]['waiting'] );
|
||||||
|
} else {
|
||||||
|
return strnatcmp( $a, $b );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
$project->icon = $this->get_plugin_icon( $project, 64 );
|
||||||
|
|
||||||
|
$this->tmpl( 'projects-wp-plugins', get_defined_vars() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints stats about contributors of a specific project.
|
||||||
|
*
|
||||||
|
* @param string $project_slug Slug of a project.
|
||||||
|
*/
|
||||||
|
public function get_plugin_contributors( $project_slug ) {
|
||||||
|
$project_path = 'wp-plugins/' . $project_slug;
|
||||||
|
$project = GP::$project->by_path( $project_path );
|
||||||
|
if ( ! $project ) {
|
||||||
|
return $this->die_with_404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$project->icon = $this->get_plugin_icon( $project, 64 );
|
||||||
|
|
||||||
|
$contributors_by_locale = gp_get_meta( 'wp-plugins', $project->id, 'contributors-by-locale' );
|
||||||
|
if ( ! $contributors_by_locale || $contributors_by_locale['last_updated'] + HOUR_IN_SECONDS < time() ) {
|
||||||
|
$contributors_by_locale = $this->get_contributors( $project );
|
||||||
|
$contributors_by_locale['last_updated'] = time();
|
||||||
|
gp_update_meta( $project->id, 'contributors-by-locale', $contributors_by_locale, 'wp-plugins' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$chart_data = gp_get_meta( 'wp-plugins', $project->id, 'contributors-chart-data' );
|
||||||
|
if ( ! $chart_data || $chart_data['last_updated'] + DAY_IN_SECONDS < time() ) {
|
||||||
|
$chart_data = $this->get_contributors_chart_data( $project );
|
||||||
|
$chart_data['last_updated'] = time();
|
||||||
|
gp_update_meta( $project->id, 'contributors-chart-data', $chart_data, 'wp-plugins' );
|
||||||
|
}
|
||||||
|
|
||||||
|
unset( $contributors_by_locale['last_updated'], $chart_data['last_updated'] );
|
||||||
|
|
||||||
|
$this->tmpl( 'projects-wp-plugins-contributors', get_defined_vars() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints stats about language packs of a specific project.
|
||||||
|
*
|
||||||
|
* @param string $project_slug Slug of a project.
|
||||||
|
*/
|
||||||
|
public function get_plugin_language_packs( $project_slug ) {
|
||||||
|
$project_path = 'wp-plugins/' . $project_slug;
|
||||||
|
$project = GP::$project->by_path( $project_path );
|
||||||
|
if ( ! $project ) {
|
||||||
|
return $this->die_with_404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$project->icon = $this->get_plugin_icon( $project, 64 );
|
||||||
|
|
||||||
|
$json = wp_remote_retrieve_body(
|
||||||
|
wp_safe_remote_get(
|
||||||
|
"https://api.wordpress.org/translations/plugins/1.0/?slug={$project_slug}",
|
||||||
|
array(
|
||||||
|
'user-agent' => 'WordPress.org Translate',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$language_packs = $json && '{' == $json[0] ? json_decode( $json ) : null;
|
||||||
|
|
||||||
|
$this->tmpl( 'projects-wp-plugins-language-packs', get_defined_vars() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the icon of a plugin.
|
||||||
|
*
|
||||||
|
* @param GP_Project $project The plugin project.
|
||||||
|
* @param int $size Optional. The size of the icon. Default 64.
|
||||||
|
* @return string HTML markup for the icon.
|
||||||
|
*/
|
||||||
|
private function get_plugin_icon( $project, $size = 64 ) {
|
||||||
|
$default = '<div class="default-icon"><span class="dashicons dashicons-admin-plugins"></span></div>';
|
||||||
|
|
||||||
|
$icon = '';
|
||||||
|
|
||||||
|
if ( class_exists( 'WordPressdotorg\Plugin_Directory\Template' ) ) {
|
||||||
|
$directory_post_id = gp_get_meta( 'wp-plugins', $project->id, 'directory-post-id' );
|
||||||
|
if ( $directory_post_id ) {
|
||||||
|
switch_to_blog( WPORG_PLUGIN_DIRECTORY_BLOGID );
|
||||||
|
$icon = \WordPressdotorg\Plugin_Directory\Template::get_plugin_icon( $directory_post_id, 'html' );
|
||||||
|
restore_current_blog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $icon ) {
|
||||||
|
return $icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace WordPressdotorg\GlotPress\Routes\Routes;
|
||||||
|
|
||||||
|
use GP;
|
||||||
|
|
||||||
|
class WP_Themes extends WP_Directory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints stats about sub-project of a specific project.
|
||||||
|
*
|
||||||
|
* @param string $project_slug Slug of a project.
|
||||||
|
*/
|
||||||
|
public function get_theme_projects( $project_slug ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$project_path = 'wp-themes/' . $project_slug;
|
||||||
|
$project = GP::$project->by_path( $project_path );
|
||||||
|
if ( ! $project ) {
|
||||||
|
return $this->die_with_404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $wpdb->get_results( "
|
||||||
|
SELECT
|
||||||
|
path, locale, locale_slug,
|
||||||
|
(100 * stats.current/stats.all) as percent_complete,
|
||||||
|
stats.waiting+stats.fuzzy as waiting_strings,
|
||||||
|
stats.untranslated as untranslated
|
||||||
|
FROM {$wpdb->project_translation_status} stats
|
||||||
|
LEFT JOIN {$wpdb->gp_projects} p ON stats.project_id = p.id
|
||||||
|
WHERE
|
||||||
|
p.id = '{$project->id}'
|
||||||
|
" );
|
||||||
|
|
||||||
|
// Split out into $[Locale][Project] = %
|
||||||
|
$translation_locale_statuses = array();
|
||||||
|
$sub_projects = array();
|
||||||
|
foreach ( $rows as $set ) {
|
||||||
|
|
||||||
|
// Find unique locale key.
|
||||||
|
$locale_key = $set->locale;
|
||||||
|
if ( 'default' != $set->locale_slug ) {
|
||||||
|
$locale_key = $set->locale . '/' . $set->locale_slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* > 50% round down, so that a project with all strings except 1 translated shows 99%, instead of 100%.
|
||||||
|
* < 50% round up, so that a project with just a few strings shows 1%, instead of 0%.
|
||||||
|
*/
|
||||||
|
$percent_complete = (float) $set->percent_complete;
|
||||||
|
$translation_locale_statuses[ $locale_key ]['stable'] = ( $percent_complete > 50 ) ? floor( $percent_complete ) : ceil( $percent_complete );
|
||||||
|
|
||||||
|
// Increment the amount of waiting and untranslated strings.
|
||||||
|
if ( ! isset( $translation_locale_statuses[ $locale_key ]['waiting'] ) ) {
|
||||||
|
$translation_locale_statuses[ $locale_key ]['waiting'] = 0;
|
||||||
|
}
|
||||||
|
if ( ! isset( $translation_locale_statuses[ $locale_key ]['untranslated'] ) ) {
|
||||||
|
$translation_locale_statuses[ $locale_key ]['untranslated'] = 0;
|
||||||
|
}
|
||||||
|
$translation_locale_statuses[ $locale_key ]['waiting'] += (int) $set->waiting_strings;
|
||||||
|
$translation_locale_statuses[ $locale_key ]['untranslated'] += (int) $set->untranslated;
|
||||||
|
|
||||||
|
|
||||||
|
ksort( $translation_locale_statuses[ $locale_key ], SORT_NATURAL );
|
||||||
|
}
|
||||||
|
|
||||||
|
unset( $project_path, $locale_key, $rows, $set, $sub_project, $sub_projects );
|
||||||
|
|
||||||
|
// Calculate a list of [Locale] = % subtotals
|
||||||
|
$translation_locale_complete = array();
|
||||||
|
foreach ( $translation_locale_statuses as $locale => $sets ) {
|
||||||
|
unset( $sets['waiting'], $sets['untranslated'] );
|
||||||
|
$translation_locale_complete[ $locale ] = round( array_sum( $sets ) / count( $sets ), 3 );
|
||||||
|
}
|
||||||
|
unset( $locale, $sets );
|
||||||
|
|
||||||
|
|
||||||
|
// Sort by translation completeness, least number of waiting strings, and locale slug.
|
||||||
|
uksort( $translation_locale_complete, function ( $a, $b ) use ( $translation_locale_complete, $translation_locale_statuses ) {
|
||||||
|
if ( $translation_locale_complete[ $a ] < $translation_locale_complete[ $b ] ) {
|
||||||
|
return 1;
|
||||||
|
} elseif ( $translation_locale_complete[ $a ] == $translation_locale_complete[ $b ] ) {
|
||||||
|
if ( $translation_locale_statuses[ $a ]['waiting'] != $translation_locale_statuses[ $b ]['waiting'] ) {
|
||||||
|
return strnatcmp( $translation_locale_statuses[ $a ]['waiting'], $translation_locale_statuses[ $b ]['waiting'] );
|
||||||
|
} else {
|
||||||
|
return strnatcmp( $a, $b );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
$project->icon = $this->get_theme_icon( $project, 64 );
|
||||||
|
|
||||||
|
$this->tmpl( 'projects-wp-themes', get_defined_vars() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints stats about contributors of a specific project.
|
||||||
|
*
|
||||||
|
* @param string $project_slug Slug of a project.
|
||||||
|
*/
|
||||||
|
public function get_theme_contributors( $project_slug ) {
|
||||||
|
global $wpdb;
|
||||||
|
|
||||||
|
$project_path = 'wp-themes/' . $project_slug;
|
||||||
|
$project = GP::$project->by_path( $project_path );
|
||||||
|
if ( ! $project ) {
|
||||||
|
return $this->die_with_404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$project->icon = $this->get_theme_icon( $project, 64 );
|
||||||
|
|
||||||
|
$contributors_by_locale = gp_get_meta( 'wp-themes', $project->id, 'contributors-by-locale' );
|
||||||
|
if ( ! $contributors_by_locale || $contributors_by_locale['last_updated'] + HOUR_IN_SECONDS < time() ) {
|
||||||
|
$contributors_by_locale = $this->get_contributors( $project );
|
||||||
|
$contributors_by_locale['last_updated'] = time();
|
||||||
|
gp_update_meta( $project->id, 'contributors-by-locale', $contributors_by_locale, 'wp-themes' );
|
||||||
|
}
|
||||||
|
|
||||||
|
$chart_data = gp_get_meta( 'wp-themes', $project->id, 'contributors-chart-data' );
|
||||||
|
if ( ! $chart_data || $chart_data['last_updated'] + DAY_IN_SECONDS < time() ) {
|
||||||
|
$chart_data = $this->get_contributors_chart_data( $project );
|
||||||
|
$chart_data['last_updated'] = time();
|
||||||
|
gp_update_meta( $project->id, 'contributors-chart-data', $chart_data, 'wp-themes' );
|
||||||
|
}
|
||||||
|
|
||||||
|
unset( $contributors_by_locale['last_updated'], $chart_data['last_updated'] );
|
||||||
|
|
||||||
|
$this->tmpl( 'projects-wp-themes-contributors', get_defined_vars() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints stats about language packs of a specific project.
|
||||||
|
*
|
||||||
|
* @param string $project_slug Slug of a project.
|
||||||
|
*/
|
||||||
|
public function get_theme_language_packs( $project_slug ) {
|
||||||
|
$project_path = 'wp-themes/' . $project_slug;
|
||||||
|
$project = GP::$project->by_path( $project_path );
|
||||||
|
if ( ! $project ) {
|
||||||
|
return $this->die_with_404();
|
||||||
|
}
|
||||||
|
|
||||||
|
$project->icon = $this->get_theme_icon( $project, 64 );
|
||||||
|
|
||||||
|
$language_packs = $this->get_language_packs( 'theme', $project_slug );
|
||||||
|
|
||||||
|
$this->tmpl( 'projects-wp-themes-language-packs', get_defined_vars() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the icon of a theme.
|
||||||
|
*
|
||||||
|
* @param GP_Project $project The theme project.
|
||||||
|
* @param int $size Optional. The size of the icon. Default 64.
|
||||||
|
* @return string HTML markup for the icon.
|
||||||
|
*/
|
||||||
|
private function get_theme_icon( $project, $size = 64 ) {
|
||||||
|
$default = '<div class="default-icon"><span class="dashicons dashicons-admin-themes"></span></div>';
|
||||||
|
|
||||||
|
$screenshot = gp_get_meta( 'wp-themes', $project->id, 'screenshot' );
|
||||||
|
if ( $screenshot ) {
|
||||||
|
return sprintf(
|
||||||
|
'<div class="icon"><img src="%s" alt="" width="%d" height="%d"></div>',
|
||||||
|
esc_url( 'https://i0.wp.com/' . $screenshot . '?w=' . $size * 2 . '&strip=all' ),
|
||||||
|
$size,
|
||||||
|
$size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Plugin name: GlotPress: Custom Routes
|
||||||
|
* Description: Provides custom routes like <code>/locale</code> or <code>/stats</code> for translate.wordpress.org.
|
||||||
|
* Version: 2.0
|
||||||
|
* Author: WordPress.org
|
||||||
|
* Author URI: http://wordpress.org/
|
||||||
|
* License: GPLv2 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace WordPressdotorg\GlotPress\Routes;
|
||||||
|
|
||||||
|
use WordPressdotorg\Autoload;
|
||||||
|
|
||||||
|
// Store the root plugin file for usage with functions which use the plugin basename.
|
||||||
|
define( __NAMESPACE__ . '\PLUGIN_FILE', __FILE__ );
|
||||||
|
|
||||||
|
if ( ! class_exists( '\WordPressdotorg\Autoload\Autoloader', false ) ) {
|
||||||
|
include __DIR__ . '/vendor/wordpressdotorg/autoload/class-autoloader.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register an Autoloader for all files.
|
||||||
|
Autoload\register_class_path( __NAMESPACE__, __DIR__ . '/inc' );
|
||||||
|
|
||||||
|
// Instantiate the Plugin.
|
||||||
|
Plugin::get_instance();
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
* Link styles
|
||||||
|
* https://github.com/WordPress/gutenberg/issues/42319
|
||||||
|
*/
|
||||||
|
a {
|
||||||
|
text-decoration-thickness: 1px !important;
|
||||||
|
text-underline-offset: .1em;
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue