Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions .github/workflows/phpunit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ jobs:
phpunit:
runs-on: ubuntu-latest

services:
mariadb:
image: mariadb:lts
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
ports:
- 3306:3306
options: >-
--health-cmd="healthcheck.sh --connect --innodb_initialized"
--health-interval=10s
--health-timeout=5s
--health-retries=3

strategy:
fail-fast: false
matrix:
Expand All @@ -35,9 +48,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}

- name: Setup Database
uses: getong/mariadb-action@v1.1
coverage: none

- name: Check PHP Version
run: php -v
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/plugin-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ jobs:
uses: wordpress/plugin-check-action@v1
with:
exclude-checks: 'trademarks,file_type,plugin_readme'
exclude-directories: '.github,bin,vendor'
exclude-directories: '.github,bin,vendor,tests'
94 changes: 94 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Google Spreadsheet to DB is a WordPress plugin that imports data from Google Sheets into WordPress database using Google's Sheets API (v4). This is a PHP/TypeScript hybrid project with a modern frontend build system.

## Development Commands

### PHP Development
```bash
# Install PHP dependencies
cd functions/composer/
composer install

# Run PHPStan static analysis
composer phpstan

# Run PHPUnit tests
./vendor/bin/phpunit

# Run PHP CodeSniffer
./vendor/bin/phpcs

# Clear PHPStan cache
composer phpstan-clear
```

### Frontend Development
```bash
# Install Node.js dependencies
pnpm install

# Development build with watch mode
pnpm dev

# Production build
pnpm build

# Type checking
pnpm type-check
pnpm type-check:watch

# Linting
pnpm lint
pnpm lint:fix
pnpm lint:css
```

## Code Architecture

### PHP Backend Structure
- **Main Plugin File**: `google-spreadsheet-to-db.php` - Entry point, handles plugin activation and includes
- **Core Classes**: Located in `includes/` directory
- `class-google-spreadsheet-to-db-query.php` - Database query interface for retrieving stored data
- `class-google-spreadsheet-to-db-activator.php` - Plugin activation logic
- `admin.php` - WordPress admin interface
- `save.php` - Data saving functionality
- `index.php` - Core plugin functionality
- **Helper Functions**: `functions/functions.php`
- **Database**: Uses custom table `wp_google_ss2db` to store spreadsheet data as JSON

### Frontend Structure
- **TypeScript Source**: `src/assets/ts/` - Modular TypeScript components
- **CSS Source**: `src/assets/css/` - PostCSS stylesheets
- **Build Tool**: Rspack (configured in `rspack.config.ts`)

### Configuration Constants
Plugin requires WordPress constants in `wp-config.php`:
- `GOOGLE_SS2DB_CLIENT_SECRET_PATH` - Path to Google API credentials JSON file
- `GOOGLE_SS2DB_DEBUG` (optional) - Enable debug mode for detailed JSON responses

## Key Features
- Imports Google Sheets data via API and stores as JSON in WordPress database
- Provides WordPress admin interface for configuration
- Offers hooks for data manipulation before/after save (`google_ss2db_before_save`, `google_ss2db_after_save`)
- Query API for retrieving stored data with filtering and sorting capabilities

## Quality Tools
- **PHP**: PHPStan (level 9), PHPCS with WordPress Coding Standards, PHPUnit
- **TypeScript**: ESLint with Airbnb config, TypeScript strict mode
- **CSS**: Stylelint with standard config
- **Formatting**: Prettier for JS/TS/CSS

## Testing
- PHP tests located in `tests/` directory
- Uses PHPUnit with WordPress test framework
- Test configuration in `phpunit.xml.dist`

## Dependencies
- PHP 8.0+ required
- Google API Client Library for PHP
- Modern Node.js toolchain (pnpm, TypeScript, Rspack)
134 changes: 69 additions & 65 deletions admin/class-recursivetable.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

declare(strict_types=1);

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* The core plugin class.
*
Expand Down Expand Up @@ -63,15 +67,15 @@
</th>
<td>
<?php
$types = array(
$google_ss2db_types = array(
'json' => 'json_encode',
'json-unescp' => 'json_encode (JSON_UNESCAPED_UNICODE)',
);
?>
<select id="google_ss2db_dataformat" name="google_ss2db_dataformat" style="font-size: 11px; width: 330px;">
<?php foreach ( $types as $key => $type ) : ?>
<?php $selected = ( get_option( 'google_ss2db_dataformat' ) === $key ) ? 'selected' : ''; ?>
<option value="<?php echo esc_attr( $key ); ?>" <?php echo esc_attr( $selected ); ?>><?php echo esc_html( $type ); ?></option>
<?php foreach ( $google_ss2db_types as $google_ss2db_key => $type ) : ?>
<?php $google_ss2db_selected = ( get_option( 'google_ss2db_dataformat' ) === $google_ss2db_key ) ? 'selected' : ''; ?>
<option value="<?php echo esc_attr( $google_ss2db_key ); ?>" <?php echo esc_attr( $google_ss2db_selected ); ?>><?php echo esc_html( $type ); ?></option>
<?php endforeach; ?>
</select>
</td>
Expand Down Expand Up @@ -157,25 +161,25 @@
<p><?php echo esc_html__( 'This process may takes a few minutes.', 'google-spreadsheet-to-db' ); ?></p>
<?php wp_nonce_field( 'google_ss2db', 'nonce' ); ?>
<?php
$text = esc_html__( 'Import from Google Spreadsheet', 'google-spreadsheet-to-db' );
submit_button( $text, 'primary', 'save-spreadsheet', false );
$google_ss2db_text = esc_html__( 'Import from Google Spreadsheet', 'google-spreadsheet-to-db' );
submit_button( $google_ss2db_text, 'primary', 'save-spreadsheet', false );
?>
</form>
</section>
<?php
global $wpdb;
$table = GOOGLE_SS2DB_TABLE_NAME;
$google_ss2db_table = GOOGLE_SS2DB_TABLE_NAME;

// Get sort parameters.
$orderby = filter_input( INPUT_GET, 'orderby', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$order = filter_input( INPUT_GET, 'order', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$google_ss2db_orderby = filter_input( INPUT_GET, 'orderby', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$google_ss2db_order = filter_input( INPUT_GET, 'order', FILTER_SANITIZE_FULL_SPECIAL_CHARS );

// Default sort settings.
$default_orderby = 'date';
$default_order = 'DESC';
$google_ss2db_default_orderby = 'date';
$google_ss2db_default_order = 'DESC';

// Allowed sort columns.
$allowed_orderby = array(
$google_ss2db_allowed_orderby = array(
'id' => 'id',
'worksheet_id' => 'worksheet_id',
'worksheet_name' => 'worksheet_name',
Expand All @@ -185,44 +189,44 @@
);

// Sort column validation.
$order = $order ?? $default_order;
$orderby = isset( $allowed_orderby[ $orderby ] ) ? $orderby : $default_orderby;
$order = in_array( strtoupper( $order ), array( 'ASC', 'DESC' ), true ) ? strtoupper( $order ) : $default_order;
$google_ss2db_order = $google_ss2db_order ?? $google_ss2db_default_order;
$google_ss2db_orderby = isset( $google_ss2db_allowed_orderby[ $google_ss2db_orderby ] ) ? $google_ss2db_orderby : $google_ss2db_default_orderby;
$google_ss2db_order = in_array( strtoupper( $google_ss2db_order ), array( 'ASC', 'DESC' ), true ) ? strtoupper( $google_ss2db_order ) : $google_ss2db_default_order;

$paged = filter_input( INPUT_GET, 'paged', FILTER_VALIDATE_INT );
$nonce = filter_input( INPUT_GET, 'nonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$google_ss2db_paged = filter_input( INPUT_GET, 'paged', FILTER_VALIDATE_INT );
$google_ss2db_nonce = filter_input( INPUT_GET, 'nonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS );

// Verify pagination nonce.
if ( $paged && ! wp_verify_nonce( $nonce, 'google_ss2db_pagination' ) ) {
$paged = 1;
if ( $google_ss2db_paged && ! wp_verify_nonce( $google_ss2db_nonce, 'google_ss2db_pagination' ) ) {
$google_ss2db_paged = 1;
}

$paged = $paged ? $paged : 1;
$limit = 24;
$offset = ( $paged - 1 ) * $limit;
$google_ss2db_paged = $google_ss2db_paged ? $google_ss2db_paged : 1;
$google_ss2db_limit = 24;
$google_ss2db_offset = ( $google_ss2db_paged - 1 ) * $google_ss2db_limit;

// SQL with sorting.
$countsql = "SELECT * FROM {$table} ORDER BY {$orderby} {$order}";
$allrows = count( $wpdb->get_results( $countsql ) ); // phpcs:ignore
$max_num_pages = ceil( $allrows / $limit );
$google_ss2db_countsql = "SELECT * FROM {$google_ss2db_table} ORDER BY {$google_ss2db_orderby} {$google_ss2db_order}";
$google_ss2db_allrows = count( $wpdb->get_results( $google_ss2db_countsql ) ); // phpcs:ignore
$google_ss2db_max_num_pages = ceil( $google_ss2db_allrows / $google_ss2db_limit );

$sql = "SELECT * FROM {$table} ORDER BY {$orderby} {$order} LIMIT %d OFFSET %d";
$prepared = $wpdb->prepare(
$sql, // phpcs:ignore
$limit,
$offset
$google_ss2db_sql = "SELECT * FROM {$google_ss2db_table} ORDER BY {$google_ss2db_orderby} {$google_ss2db_order} LIMIT %d OFFSET %d";
$google_ss2db_prepared = $wpdb->prepare(
$google_ss2db_sql, // phpcs:ignore
$google_ss2db_limit,
$google_ss2db_offset
);

$myrows = $wpdb->get_results( $prepared ); // phpcs:ignore
$count = count( $myrows );
$google_ss2db_myrows = $wpdb->get_results( $google_ss2db_prepared ); // phpcs:ignore
$google_ss2db_count = count( $google_ss2db_myrows );

/**
* Generate sort URLs for table columns.
*
* @param string $column The column to generate sort URL for.
* @return string The generated sort URL.
*/
function get_sort_url( string $column ): string {
function google_ss2db_get_sort_url( string $column ): string {
$current_page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$current_orderby = filter_input( INPUT_GET, 'orderby', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
$current_orderby = $current_orderby ? $current_orderby : 'date';
Expand All @@ -241,45 +245,45 @@ function get_sort_url( string $column ): string {
return esc_url( add_query_arg( $url_params ) );
}

if ( 0 < $count ) :
if ( 0 < $google_ss2db_count ) :
?>
<section id="list">
<hr />
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th scope="col" class="manage-column sortable <?php echo esc_attr( 'id' === $orderby ? 'sorted ' . strtolower( $order ) : '' ); ?>">
<a href="<?php echo esc_url( get_sort_url( 'id' ) ); ?>">
<th scope="col" class="manage-column sortable <?php echo esc_attr( 'id' === $google_ss2db_orderby ? 'sorted ' . strtolower( $google_ss2db_order ) : '' ); ?>">
<a href="<?php echo esc_url( google_ss2db_get_sort_url( 'id' ) ); ?>">
<span>ID</span>
<span class="sorting-indicator"></span>
</a>
</th>
<th scope="col" class="manage-column sortable <?php echo esc_attr( 'worksheet_id' === $orderby ? 'sorted ' . strtolower( $order ) : '' ); ?>">
<a href="<?php echo esc_url( get_sort_url( 'worksheet_id' ) ); ?>">
<th scope="col" class="manage-column sortable <?php echo esc_attr( 'worksheet_id' === $google_ss2db_orderby ? 'sorted ' . strtolower( $google_ss2db_order ) : '' ); ?>">
<a href="<?php echo esc_url( google_ss2db_get_sort_url( 'worksheet_id' ) ); ?>">
<span>Worksheet ID</span>
<span class="sorting-indicator"></span>
</a>
</th>
<th scope="col" class="manage-column sortable <?php echo esc_attr( 'worksheet_name' === $orderby ? 'sorted ' . strtolower( $order ) : '' ); ?>">
<a href="<?php echo esc_url( get_sort_url( 'worksheet_name' ) ); ?>">
<th scope="col" class="manage-column sortable <?php echo esc_attr( 'worksheet_name' === $google_ss2db_orderby ? 'sorted ' . strtolower( $google_ss2db_order ) : '' ); ?>">
<a href="<?php echo esc_url( google_ss2db_get_sort_url( 'worksheet_name' ) ); ?>">
<span>Worksheet Name</span>
<span class="sorting-indicator"></span>
</a>
</th>
<th scope="col" class="manage-column sortable <?php echo esc_attr( 'sheet_name' === $orderby ? 'sorted ' . strtolower( $order ) : '' ); ?>">
<a href="<?php echo esc_url( get_sort_url( 'sheet_name' ) ); ?>">
<th scope="col" class="manage-column sortable <?php echo esc_attr( 'sheet_name' === $google_ss2db_orderby ? 'sorted ' . strtolower( $google_ss2db_order ) : '' ); ?>">
<a href="<?php echo esc_url( google_ss2db_get_sort_url( 'sheet_name' ) ); ?>">
<span>Sheet Name</span>
<span class="sorting-indicator"></span>
</a>
</th>
<th scope="col" class="manage-column sortable <?php echo esc_attr( 'title' === $orderby ? 'sorted ' . strtolower( $order ) : '' ); ?>">
<a href="<?php echo esc_url( get_sort_url( 'title' ) ); ?>">
<th scope="col" class="manage-column sortable <?php echo esc_attr( 'title' === $google_ss2db_orderby ? 'sorted ' . strtolower( $google_ss2db_order ) : '' ); ?>">
<a href="<?php echo esc_url( google_ss2db_get_sort_url( 'title' ) ); ?>">
<span>Title</span>
<span class="sorting-indicator"></span>
</a>
</th>
<th scope="col" class="manage-column sortable <?php echo esc_attr( 'date' === $orderby ? 'sorted ' . strtolower( $order ) : '' ); ?>">
<a href="<?php echo esc_url( get_sort_url( 'date' ) ); ?>">
<th scope="col" class="manage-column sortable <?php echo esc_attr( 'date' === $google_ss2db_orderby ? 'sorted ' . strtolower( $google_ss2db_order ) : '' ); ?>">
<a href="<?php echo esc_url( google_ss2db_get_sort_url( 'date' ) ); ?>">
<span>Date</span>
<span class="sorting-indicator"></span>
</a>
Expand All @@ -288,31 +292,31 @@ function get_sort_url( string $column ): string {
</tr>
</thead>
<tbody>
<?php foreach ( $myrows as $row ) : ?>
<tr data-id="<?php echo esc_attr( $row->id ); ?>">
<td><?php echo esc_html( $row->id ); ?></td>
<td><?php echo esc_html( google_ss2db_truncate_middle( $row->worksheet_id ?? '(no ID)' ) ); ?></td>
<td><?php echo esc_html( $row->worksheet_name ); ?></td>
<td><?php echo esc_html( $row->sheet_name ); ?></td>
<td style="color: <?php echo $row->title ? 'inherit' : '#aaa'; ?>">
<?php echo esc_html( $row->title ? $row->title : '(no title)' ); ?>
<?php foreach ( $google_ss2db_myrows as $google_ss2db_row ) : ?>
<tr data-id="<?php echo esc_attr( $google_ss2db_row->id ); ?>">
<td><?php echo esc_html( $google_ss2db_row->id ); ?></td>
<td><?php echo esc_html( google_ss2db_truncate_middle( $google_ss2db_row->worksheet_id ?? '(no ID)' ) ); ?></td>
<td><?php echo esc_html( $google_ss2db_row->worksheet_name ); ?></td>
<td><?php echo esc_html( $google_ss2db_row->sheet_name ); ?></td>
<td style="color: <?php echo $google_ss2db_row->title ? 'inherit' : '#aaa'; ?>">
<?php echo esc_html( $google_ss2db_row->title ? $google_ss2db_row->title : '(no title)' ); ?>
</td>
<td>
<?php
$date = new DateTime( $row->date );
$date_format = is_string( get_option( 'date_format' ) ) ? get_option( 'date_format' ) : 'Y-m-d';
$time_format = is_string( get_option( 'time_format' ) ) ? get_option( 'time_format' ) : 'H:i:s';
echo esc_html( date_i18n( $date_format . ' ' . $time_format, $date->getTimestamp() ) );
$google_ss2db_date = new DateTime( $google_ss2db_row->date );
$google_ss2db_date_format = is_string( get_option( 'date_format' ) ) ? get_option( 'date_format' ) : 'Y-m-d';
$google_ss2db_time_format = is_string( get_option( 'time_format' ) ) ? get_option( 'time_format' ) : 'H:i:s';
echo esc_html( date_i18n( $google_ss2db_date_format . ' ' . $google_ss2db_time_format, $google_ss2db_date->getTimestamp() ) );
?>
</td>
<td>
<button class="button view-details" data-id="<?php echo esc_attr( $row->id ); ?>">
<button class="button view-details" data-id="<?php echo esc_attr( $google_ss2db_row->id ); ?>">
<?php echo esc_html__( 'Details', 'google-spreadsheet-to-db' ); ?>
</button>
<button class="button view-raw-data" data-id="<?php echo esc_attr( $row->id ); ?>">
<button class="button view-raw-data" data-id="<?php echo esc_attr( $google_ss2db_row->id ); ?>">
<?php echo esc_html__( 'Raw Data', 'google-spreadsheet-to-db' ); ?>
</button>
<button class="button delete-entry" data-id="<?php echo esc_attr( $row->id ); ?>">
<button class="button delete-entry" data-id="<?php echo esc_attr( $google_ss2db_row->id ); ?>">
<?php echo esc_html__( 'Delete', 'google-spreadsheet-to-db' ); ?>
</button>
</td>
Expand All @@ -322,13 +326,13 @@ function get_sort_url( string $column ): string {
</table>

<?php
$pagination_nonce = esc_attr( wp_create_nonce( 'google_ss2db_pagination' ) );
$google_ss2db_pagination_nonce = esc_attr( wp_create_nonce( 'google_ss2db_pagination' ) );
if ( function_exists( 'google_ss2db_options_pagination' ) ) {
google_ss2db_options_pagination(
$paged,
(int) $max_num_pages,
$google_ss2db_paged,
(int) $google_ss2db_max_num_pages,
2,
$pagination_nonce
$google_ss2db_pagination_nonce
);
}
?>
Expand Down
1 change: 0 additions & 1 deletion bin/install-wp-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ install_wp() {
tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR
fi

download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
}

install_test_suite() {
Expand Down
Loading