From 1da259271a49b163c6dd86bfa1cf95b486cdf47f Mon Sep 17 00:00:00 2001
From: fumikito <guy@hametuha.com>
Date: Mon, 12 Feb 2024 15:14:56 +0900
Subject: [PATCH 1/6] =?UTF-8?q?=E3=83=87=E3=83=97=E3=83=AD=E3=82=A4?=
 =?UTF-8?q?=E3=81=A7rsync=E3=81=AB=E5=A4=B1=E6=95=97=E3=81=97=E3=81=9F?=
 =?UTF-8?q?=E3=81=AE=E3=81=A7=E3=80=81=E3=83=9D=E3=83=BC=E3=83=88=E7=95=AA?=
 =?UTF-8?q?=E5=8F=B7=E3=82=92=E4=BF=AE=E6=AD=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .github/workflows/wordpress.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/wordpress.yml b/.github/workflows/wordpress.yml
index 9d07848..9a06bfa 100644
--- a/.github/workflows/wordpress.yml
+++ b/.github/workflows/wordpress.yml
@@ -78,7 +78,7 @@ jobs:
         with:
           flags: '-rptv --checksum --delete'
           options: '--exclude-from=.distignore'
-          ssh_options: ''
+          ssh_options: '-p 2222'
           src: './'
           dest: "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/home/users/2/${{ secrets.DEPLOY_USER }}/web/wp-checkin/wp-content/plugins/${{ github.event.repository.name }}/"
 

From 59f4347e4f7cd3a43174f60364ce001cda2e4914 Mon Sep 17 00:00:00 2001
From: fumikito <guy@hametuha.com>
Date: Mon, 12 Feb 2024 16:18:08 +0900
Subject: [PATCH 2/6] =?UTF-8?q?#8=20Basic=E8=AA=8D=E8=A8=BC=E3=82=92?=
 =?UTF-8?q?=E3=81=8B=E3=81=91=E3=82=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/WCTokyo/WpCheckin/Router.php         | 30 +++++++++++++++++++++++-
 lib/WCTokyo/WpCheckin/Screen/Setting.php |  2 +-
 lib/WCTokyo/WpCheckin/Tickets.php        |  5 ++--
 3 files changed, 32 insertions(+), 5 deletions(-)

diff --git a/lib/WCTokyo/WpCheckin/Router.php b/lib/WCTokyo/WpCheckin/Router.php
index 416a5f5..1c7ba51 100644
--- a/lib/WCTokyo/WpCheckin/Router.php
+++ b/lib/WCTokyo/WpCheckin/Router.php
@@ -21,6 +21,7 @@ protected function init() {
 		add_action( 'init', [ $this, 'add_rewrite_rules' ] );
 		add_filter( 'query_vars', [ $this, 'add_query_vars' ] );
 		add_action( 'pre_get_posts', [ $this, 'pre_get_posts' ] );
+		add_action( 'admin_bar_menu', [ $this, 'admin_bar_menu' ], 300 );
 	}
 
 	/**
@@ -109,6 +110,33 @@ public function add_rewrite_rules() {
 	 * @return void
 	 */
 	public function do_authorization_header() {
-		// W.I.P
+		$user = get_option( 'wordcamp_auth_user' );
+		$pass = get_option( 'wordcamp_auth_pass' );
+		if ( ! isset( $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ) || $user !== $_SERVER['PHP_AUTH_USER'] || $pass !== $_SERVER['PHP_AUTH_PW'] ) {
+			header( 'WWW-Authenticate: Basic realm="Enter username and password."' );
+			header( 'Content-Type: text/plain; charset=utf-8' );
+			wp_die( __( 'このページを閲覧するためにはユーザー名とパスワードが必要です。', 'wp-checkin' ), get_status_header_desc( 401 ), [
+				'status'   => 401,
+				'response' => 401,
+			] );
+		}
+	}
+
+	/**
+	 * Custom admin bar.
+	 *
+	 * @param \WP_Admin_Bar $admin_bar Admin bar instance.
+	 * @return void
+	 */
+	public function admin_bar_menu( \WP_Admin_Bar &$admin_bar ) {
+		$admin_bar->add_node( [
+			'parent' => 'site-name',
+			'id'     => 'wp-checkin',
+			'title'  => __( 'チケット一覧ページ', 'wp=-checkin' ),
+			'href'   => home_url( 'checkin' ),
+			'meta'   => [
+				'tabindex' => 0,
+			],
+		] );
 	}
 }
diff --git a/lib/WCTokyo/WpCheckin/Screen/Setting.php b/lib/WCTokyo/WpCheckin/Screen/Setting.php
index 184f6cb..f463472 100644
--- a/lib/WCTokyo/WpCheckin/Screen/Setting.php
+++ b/lib/WCTokyo/WpCheckin/Screen/Setting.php
@@ -34,7 +34,7 @@ public function add_menu() {
 			?>
 			<div class="wrap">
 				<h1><?php esc_html_e( 'WordCamp チェックイン設定', 'wp-checkin' ); ?></h1>
-				<form method="post" action="options.php">
+				<form method="post" action="<?php echo admin_url( 'options.php' ); ?>">
 					<?php
 					settings_fields( 'wp-checkin' );
 					do_settings_sections( 'wp-checkin' );
diff --git a/lib/WCTokyo/WpCheckin/Tickets.php b/lib/WCTokyo/WpCheckin/Tickets.php
index cadf67c..f7ec950 100644
--- a/lib/WCTokyo/WpCheckin/Tickets.php
+++ b/lib/WCTokyo/WpCheckin/Tickets.php
@@ -98,14 +98,13 @@ public static function get_meta( $ticket ) {
 			return [];
 		}
 		$meta       = [];
-		$prohibited = [
+		$prohibited = apply_filters( 'wp_checking_ignored_column_index', [
 			0,
 			1,
 			2,
 			3, // ID, 名前、メール
 			8, // トランザクションID
-
-		];
+		] );
 		foreach ( $tickets[0] as $index => $label ) {
 			// phpcs:ignore WordPress.PHP.StrictInArray.FoundNonStrictFalse
 			if ( ! in_array( $index, $prohibited, false ) ) {

From be8bf9daa87d25633f88739c44b0ee199c086ceb Mon Sep 17 00:00:00 2001
From: fumikito <guy@hametuha.com>
Date: Mon, 12 Feb 2024 17:09:39 +0900
Subject: [PATCH 3/6] =?UTF-8?q?#8=20QR=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92?=
 =?UTF-8?q?=E8=A1=A8=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/WCTokyo/WpCheckin/Router.php  | 41 ++++++++++++++++++++++++++++++-
 lib/WCTokyo/WpCheckin/Tickets.php | 19 +++++++++++---
 2 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/lib/WCTokyo/WpCheckin/Router.php b/lib/WCTokyo/WpCheckin/Router.php
index 1c7ba51..18d631a 100644
--- a/lib/WCTokyo/WpCheckin/Router.php
+++ b/lib/WCTokyo/WpCheckin/Router.php
@@ -46,12 +46,15 @@ public function pre_get_posts( $wp_query ) {
 		if ( ! get_query_var( 'checkin' ) || ! $wp_query->is_main_query() ) {
 			return;
 		}
-		if ( in_array( $is_checkin, [ 'archive', 'single' ], true ) ) {
+		if ( in_array( $is_checkin, [ 'archive', 'single', 'qr' ], true ) ) {
 			$do_auth_header = true;
 			wp_enqueue_style( 'wp-checkin' );
 			// Load template and exit.
 			$args = [];
 			switch ( $is_checkin ) {
+				case 'qr':
+					$this->render_qr();
+					break;
 				case 'archive':
 					$args = [
 						'title' => __( '登録済みのチケット', 'wp-checkin' ),
@@ -100,6 +103,7 @@ public function pre_get_posts( $wp_query ) {
 	public function add_rewrite_rules() {
 		// Front archive.
 		add_rewrite_rule( '^checkin/?$', 'index.php?checkin=archive', 'top' );
+		add_rewrite_rule( '^checkin/qr.png/?$', 'index.php?checkin=qr', 'top' );
 		add_rewrite_rule( '^checkin/page/(\d+)/?$', 'index.php?checkin=archive&paged=$matches[1]', 'top' );
 		add_rewrite_rule( '^checkin/ticket/(\d+)/?$', 'index.php?checkin=single&p=$matches[1]', 'top' );
 	}
@@ -139,4 +143,39 @@ public function admin_bar_menu( \WP_Admin_Bar &$admin_bar ) {
 			],
 		] );
 	}
+
+	/**
+	 * Render QR code.
+	 *
+	 * @return void
+	 */
+	public function render_qr() {
+		$url    = home_url( 'checkin' );
+		$params = [
+			'f' => 2,
+			'g' => 3,
+			'e' => 4,
+		];
+		$query = [];
+		foreach ( $params as $name => $index ) {
+			$query[ $index ] = filter_input( INPUT_GET, $name );
+		}
+		$tickets = Tickets::search( $query );
+		if ( 1 === count( $tickets ) ) {
+			$url = home_url( 'checkin/' . $tickets[0][0] );
+		} elseif ( ! empty( $query[4] ) ) {
+			// Not found. Try to search with email.
+			$url = home_url( 'checkin/?s=' . rawurlencode( $query[4] ) );
+		}
+		// Generate URL with Google Chart API.
+		$api_url     = add_query_arg( [
+			'cht' => 'qr',
+			'chs' => '300x300',
+			'chl' => $url,
+		], 'https://chart.apis.google.com/chart' );
+		$content = file_get_contents( $api_url );
+		header( 'Content-Type: image/png' );
+		echo $content;
+		exit;
+	}
 }
diff --git a/lib/WCTokyo/WpCheckin/Tickets.php b/lib/WCTokyo/WpCheckin/Tickets.php
index f7ec950..5cef437 100644
--- a/lib/WCTokyo/WpCheckin/Tickets.php
+++ b/lib/WCTokyo/WpCheckin/Tickets.php
@@ -52,13 +52,26 @@ public static function tickets( $include_header = true ) {
 	/**
 	 * Search ticket for the criteria
 	 *
-	 * @param $query
-	 * @param $page
+	 * @param string|array $query Search query or an array consists of column index and value.
+	 * @param int $page
 	 *
 	 * @return array{tickets:array, page:int, current:int, total:int}
 	 */
 	public static function search( $query = '', $page = 1 ) {
-		if ( $query ) {
+		if ( is_array( $query ) ) {
+			// This is index-column search.
+			$tickets = array_filter( self::tickets( false ), function( $ticket ) use ( $query ) {
+				$not_found = false;
+				foreach ( $query as $index => $value ) {
+					if ( ! isset( $ticket[ $index ] ) || $ticket[ $index ] != $value ) {
+						$not_found = true;
+						break;
+					}
+				}
+				return ! $not_found;
+			} );
+		} elseif ( $query ) {
+			// This is string search.
 			$tickets = array_filter( self::tickets( false ), function( $ticket ) use ( $query ) {
 				// Flatten array.
 				$str = implode( '', $ticket );

From 41a1072c1e9f549c33bb8d4b607065195d545665 Mon Sep 17 00:00:00 2001
From: fumikito <guy@hametuha.com>
Date: Mon, 12 Feb 2024 17:29:31 +0900
Subject: [PATCH 4/6] =?UTF-8?q?#8=20QR=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92?=
 =?UTF-8?q?=E8=A1=A8=E7=A4=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/WCTokyo/WpCheckin/FireBase.php  |  50 ----
 lib/WCTokyo/WpCheckin/Router.php    |   8 +-
 lib/WCTokyo/WpCheckin/TicketApi.php | 411 ----------------------------
 lib/WCTokyo/WpCheckin/Tickets.php   |   8 +-
 4 files changed, 8 insertions(+), 469 deletions(-)
 delete mode 100644 lib/WCTokyo/WpCheckin/FireBase.php
 delete mode 100644 lib/WCTokyo/WpCheckin/TicketApi.php

diff --git a/lib/WCTokyo/WpCheckin/FireBase.php b/lib/WCTokyo/WpCheckin/FireBase.php
deleted file mode 100644
index 5bd6ac5..0000000
--- a/lib/WCTokyo/WpCheckin/FireBase.php
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-
-namespace WCTokyo\WpCheckin;
-
-
-use Hametuha\SingletonPattern\Singleton;
-use Kreait\Firebase\Factory;
-use Google\Cloud\Firestore\FirestoreClient;
-
-/**
- * Firebase connector
- *
- * @deprecated
- */
-class FireBase extends Singleton {
-
-	protected $credential_file_path = '';
-
-	protected $database_url = '';
-
-	protected $db = null;
-
-	/**
-	 * Handle credential errors.
-	 *
-	 * @param string $setting_file_path
-	 * @param string $uri
-	 * @throws \Exception
-	 */
-	public function setCredentials( $setting_file_path, $uri ) {
-		$this->credential_file_path = $setting_file_path;
-		$this->database_url         = $uri;
-	}
-
-	/**
-	 * Get database client.
-	 *
-	 * @return FirestoreClient
-	 */
-	public function db() {
-		if ( is_null( $this->db ) ) {
-			$factory   = ( new Factory() )
-				->withServiceAccount( $this->credential_file_path )
-				->withDatabaseUri( $this->database_url );
-			$firestore = $factory->createFirestore();
-			$this->db  = $firestore->database();
-		}
-		return $this->db;
-	}
-}
diff --git a/lib/WCTokyo/WpCheckin/Router.php b/lib/WCTokyo/WpCheckin/Router.php
index 18d631a..adae60c 100644
--- a/lib/WCTokyo/WpCheckin/Router.php
+++ b/lib/WCTokyo/WpCheckin/Router.php
@@ -152,8 +152,8 @@ public function admin_bar_menu( \WP_Admin_Bar &$admin_bar ) {
 	public function render_qr() {
 		$url    = home_url( 'checkin' );
 		$params = [
-			'f' => 2,
-			'g' => 3,
+			'g' => 2,
+			'f' => 3,
 			'e' => 4,
 		];
 		$query = [];
@@ -161,8 +161,8 @@ public function render_qr() {
 			$query[ $index ] = filter_input( INPUT_GET, $name );
 		}
 		$tickets = Tickets::search( $query );
-		if ( 1 === count( $tickets ) ) {
-			$url = home_url( 'checkin/' . $tickets[0][0] );
+		if ( 1 === $tickets['total'] ) {
+			$url = home_url( 'checkin/' . $tickets['tickets'][0][0] );
 		} elseif ( ! empty( $query[4] ) ) {
 			// Not found. Try to search with email.
 			$url = home_url( 'checkin/?s=' . rawurlencode( $query[4] ) );
diff --git a/lib/WCTokyo/WpCheckin/TicketApi.php b/lib/WCTokyo/WpCheckin/TicketApi.php
deleted file mode 100644
index 9586089..0000000
--- a/lib/WCTokyo/WpCheckin/TicketApi.php
+++ /dev/null
@@ -1,411 +0,0 @@
-<?php
-
-namespace WCTokyo\WpCheckin;
-
-
-use Google\Cloud\Firestore\DocumentReference;
-use Google\Cloud\Firestore\DocumentSnapshot;
-use Hametuha\SingletonPattern\Singleton;
-use Slim\Http\Request;
-use Slim\Http\Response;
-use Slim\Http\UploadedFile;
-
-/**
- * チケットのAPI
- *
- * @deprecated
- */
-class TicketApi extends Singleton {
-
-	/**
-	 * Search ticket.
-	 *
-	 * @param Request $request
-	 * @param Response $response
-	 * @param array $args
-	 * @return Response
-	 */
-	public function handle_search( Request $request, Response $response, array $args ) {
-		try {
-			$query = $request->getQueryParam( 's' );
-			if ( ! $query ) {
-				throw new \Exception( '検索キーワードが指定されていません。', 404 );
-			}
-			$query  = explode( ' ', str_replace( ' ', ' ', $query ) );
-			$result = $this->search( $query );
-			return $response->withJson( $result );
-		} catch ( \Exception $e ) {
-			return $response->withJson( [], 404 );
-		}
-	}
-
-	/**
-	 * Handle CSV request.
-	 *
-	 * @param Request $request
-	 * @param Response $response
-	 * @param array $args
-	 */
-	public function handle_qr( Request $request, Response $response, array $args ) {
-		try {
-			$queries = [];
-			foreach ( [ 'f', 'g', 'e' ] as $key ) {
-				$param = $request->getQueryParam( $key );
-				if ( $param ) {
-					$queries[] = $param;
-				}
-			}
-			if ( ! $queries ) {
-				throw new \Exception( 'No queries set.' );
-			}
-			$result = $this->search( $queries );
-			if ( 1 !== count( $result ) ) {
-				throw new \Exception( 'Not found.' );
-			}
-			list( $data ) = $result;
-			$url          = sprintf( 'https://2019.tokyo.wp-checkin.com/ticket/%d', $data['id'] );
-		} catch ( \Exception $e ) {
-			$url = 'https://2019.tokyo.wp-checkin.com';
-		} finally {
-			$src     = str_replace( '&amp;', '&', $this->generate_qr( $url ) );
-			$content = file_get_contents( $src );
-			header( 'Content-Type: image/png' );
-			echo $content;
-			exit;
-		}
-	}
-
-	/**
-	 * Generate image url of qr code.
-	 *
-	 * @param string $text
-	 *
-	 * @return string
-	 */
-	public function generate_qr( $text ) {
-		$url     = 'https://chart.apis.google.com/chart?';
-		$queries = [];
-		foreach ( [
-			'cht' => 'qr',
-			'chs' => '300x300',
-			'chl' => $text,
-		] as $key => $val ) {
-			$queries[] = sprintf( '%s=%s', $key, rawurlencode( $val ) );
-		}
-		$url .= implode( '&amp;', $queries );
-		return $url;
-	}
-
-	/**
-	 * Search tickets.
-	 *
-	 * @param string[] $query
-	 *
-	 * @return array[]
-	 */
-	private function search( $query ) {
-		$result  = [];
-		$tickets = FireBase::get_instance()
-						->db()
-						->collection( 'Tickets' )
-						->documents();
-		foreach ( $tickets as $ticket ) {
-			/** @var DocumentSnapshot $ticket */
-			if ( ! $ticket->exists() ) {
-				continue;
-			}
-			$data   = $this->convert_to_array( $ticket );
-			$string = implode( '', $data );
-			foreach ( $query as $q ) {
-				if ( false === strpos( $string, $q ) ) {
-					continue 2;
-				}
-			}
-			$result[] = $data;
-		}
-		return $result;
-	}
-
-	/**
-	 * Returns JSON.
-	 *
-	 * @param Request $request
-	 * @param Response $response
-	 * @param array $args
-	 * @return Response
-	 */
-	public function handle_get( Request $request, Response $response, array $args ) {
-		$document = $this->get_document( $args['ticket_id'] );
-		if ( $document ) {
-			$document = $this->add_items( $document );
-			return $response->withJson( $document );
-		} else {
-			return $response->withJson( null, 404 );
-		}
-	}
-
-	/**
-	 * Handle post request.
-	 *
-	 * @param Request $request
-	 * @param Response $response
-	 * @param array $args
-	 * @return Response
-	 */
-	public function handle_post( Request $request, Response $response, array $args ) {
-		try {
-			$document = $this->get_reference( $args['ticket_id'] );
-			if ( ! $document->snapshot()->exists() ) {
-				throw new \Exception( '該当するチケットが存在しません。', 404 );
-			}
-			$document->update( [
-				[
-					'path'  => 'checkedin',
-					'value' => date_i18n( 'Y-m-d H:i:s' ),
-				],
-			] );
-			return $response->withJson( $this->add_items( $this->convert_to_array( $document->snapshot() ) ) );
-		} catch ( \Exception $e ) {
-			return $response->withJson( [
-				'message' => $e->getMessage(),
-			], $e->getCode() );
-		}
-	}
-
-	/**
-	 * Uncheck document.
-	 *
-	 * @param Request $request
-	 * @param Response $response
-	 * @param array $args
-	 * @return Response
-	 */
-	public function handle_delete( Request $request, Response $response, array $args ) {
-		try {
-			$document = $this->get_reference( $args['ticket_id'] );
-			if ( ! $document->snapshot()->exists() ) {
-				throw new \Exception( '該当するチケットが存在しません。', 404 );
-			}
-			$document->update( [
-				[
-					'path'  => 'checkedin',
-					'value' => '',
-				],
-			] );
-			return $response->withJson( $this->convert_to_array( $document->snapshot() ) );
-		} catch ( \Exception $e ) {
-			return $response->withJson( [
-				'message' => $e->getMessage(),
-			], $e->getCode() );
-		}
-	}
-
-	/**
-	 * Handle CSV request.
-	 *
-	 * @param Request $request
-	 * @param Response $response
-	 * @param array $args
-	 * @return void|Response
-	 */
-	public function handle_csv( Request $request, Response $response, array $args ) {
-		try {
-			$uploaded_files = $request->getUploadedFiles();
-			if ( empty( $uploaded_files['stat-csv'] ) ) {
-				throw new \Exception( 'CSVファイルが指定されていません。', 400 );
-			}
-			/* @var UploadedFile $file */
-			$file = $uploaded_files['stat-csv'];
-			if ( 'text/csv' !== $file->getClientMediaType() ) {
-				throw new \Exception( 'CSVファイルの形式が不正です。', 400 );
-			}
-			// List checked in time.
-			$updated = [];
-			$tickets = FireBase::get_instance()
-				->db()
-				->collection( 'Tickets' )
-				->documents();
-			foreach ( $tickets as $ticket ) {
-				/** @var DocumentSnapshot $ticket */
-				if ( ! $ticket->exists() ) {
-					continue;
-				}
-				$data = $this->convert_to_array( $ticket );
-				if ( ! empty( $data['checkedin'] ) ) {
-					$updated[ $data['id'] ] = $data['checkedin'];
-				}
-			}
-			// Read CSV.
-			$pointer = new \SplFileObject( $file->file );
-			$pointer->setFlags( \SplFileObject::READ_CSV );
-			$output = fopen( 'php://output', 'w' );
-			header( 'Content-Type: text/csv; charset=UTF-8' );
-			header( sprintf( 'Content-Disposition: attachment; filename=wp-checkin-stats-%s.csv', date_i18n( 'Ymd' ) ) );
-			// Output CSV headers.
-			fputcsv( $output, [
-				'id',
-				'status',
-				'type',
-				'issued_for',
-				'bought_by',
-				'bought_at',
-				'participated',
-				'adult',
-				'checked_in_at',
-			] );
-			// Parse CSV.
-			foreach ( $pointer as $row ) {
-				// Skip first line.
-				if ( 1 > $pointer->key() || $pointer->eof() ) {
-					continue;
-				}
-				// Get data.
-				$id          = $row[0];
-				$mail        = md5( $row[4] );
-				$mail_bought = md5( $row[11] );
-				$bought_at   = $row[5];
-				$status      = $row[7];
-				$coupon      = $row[9];
-				$title       = $row[1];
-				$over_20     = $row[16];
-				$submit      = $row[20];
-				// Type of attendee.
-				if ( false !== strpos( $coupon, 'sponsor' ) ) {
-					$type = 'sponsor';
-				} elseif ( false !== strpos( $coupon, 'staff' ) ) {
-					$type = 'staff';
-				} elseif ( false !== strpos( $coupon, 'thanks' ) ) {
-					$type = 'thanks';
-				} elseif ( false !== strpos( $title, 'マイクロスポンサー' ) ) {
-					$type = 'sponsor';
-				} else {
-					$type = 'general';
-				}
-				$participated = ( 'Yes' === $submit || isset( $updated[ $id ] ) ) ? 1 : 0;
-				if ( isset( $updated[ $id ] ) ) {
-					$gmt = $updated[ $id ];
-					// TODO: Offset.
-					$checked_in = date_i18n( 'Y-m-d H:i:s', strtotime( $gmt ) + 60 * 60 * 9 );
-				} else {
-					$checked_in = '';
-				}
-				$is_adult = ( false !== strpos( $over_20, 'Yes' ) ) ? 1 : 0;
-				$data     = [
-					$id,
-					$status,
-					$type,
-					$mail,
-					$mail_bought,
-					$bought_at,
-					$participated,
-					$is_adult,
-					$checked_in,
-				];
-				fputcsv( $output, $data );
-			}
-			exit;
-		} catch ( \Exception $e ) {
-			return $response->withStatus( $e->getCode() )
-				->withHeader( 'Content-Type', 'text/html' )
-				->write( $e->getMessage() );
-		}
-	}
-
-	/**
-	 * Get document snapshot.
-	 *
-	 * @param string $ticket_id
-	 *
-	 * @return DocumentReference
-	 */
-	protected function get_reference( $ticket_id ) {
-		return FireBase::get_instance()
-							->db()
-							->collection( 'Tickets' )
-							->document( $ticket_id );
-	}
-
-	/**
-	 * Get document.
-	 *
-	 * @param string $ticket_id
-	 * @return array
-	 */
-	protected function get_document( $ticket_id ) {
-		$document = $this->get_reference( $ticket_id )->snapshot();
-		if ( $document->exists() ) {
-			return $this->convert_to_array( $document );
-		} else {
-			return [];
-		}
-	}
-
-	/**
-	 * Convert user data to array.
-	 *
-	 * @param DocumentSnapshot $document
-	 *
-	 * @return array
-	 */
-	public function convert_to_array( $document ) {
-		$data       = $document->data();
-		$data['id'] = $document->id();
-		// Add role.
-		$role = '一般参加';
-		foreach ( [
-			'wct-sponsor-2019' => 'スポンサー',
-			'wct-staff-2019'   => 'スタッフ',
-			'wct-speaker-2019' => 'スピーカー',
-		] as $coupon => $label ) {
-			if ( isset( $data['coupon'] ) && false !== strpos( $data['coupon'], $coupon ) ) {
-				$role = $label;
-				break;
-			}
-		}
-		if ( false !== strpos( $data['category'], 'マイクロスポンサー' ) ) {
-			$role = 'マイクロスポンサー';
-		}
-		$data['role'] = $role;
-		$sorted       = [
-			'familyname' => $data['familyname'],
-			'givenname'  => $data['givenname'],
-		];
-		foreach ( $data as $key => $val ) {
-			if ( in_array( $key, [ 'familyname', 'givenname' ], true ) ) {
-				continue;
-			}
-			$sorted[ $key ] = $val;
-		}
-		return $sorted;
-	}
-
-	/**
-	 * Convert array
-	 *
-	 * @param array $document
-	 *
-	 * @return array
-	 */
-	public function add_items( $document ) {
-		$document['items'] = [
-			'パンフレット',
-			'ストラップ',
-			'ギグバンド' . ( $document['u20'] ? '(緑)' : '(黄色)' ),
-			'ナップサック',
-		];
-		if ( false !== strpos( $document['role'], 'スポンサー' ) ) {
-			$tshirt = 'Tシャツ(グレイ)';
-			if ( ! empty( $document['tshirtsize'] ) ) {
-				$tshirt .= ' - ' . $document['tshirtsize'];
-			} else {
-				$tshirt .= ' - 要サイズ確認';
-			}
-			$document['items'][] = $tshirt;
-		}
-		if ( false !== strpos( $document['role'], 'スピーカー' ) ) {
-			$document['items'][] = 'Tシャツ(緑) - 要サイズ確認';
-		}
-
-		return $document;
-	}
-}
diff --git a/lib/WCTokyo/WpCheckin/Tickets.php b/lib/WCTokyo/WpCheckin/Tickets.php
index 5cef437..3f6ca01 100644
--- a/lib/WCTokyo/WpCheckin/Tickets.php
+++ b/lib/WCTokyo/WpCheckin/Tickets.php
@@ -60,7 +60,7 @@ public static function tickets( $include_header = true ) {
 	public static function search( $query = '', $page = 1 ) {
 		if ( is_array( $query ) ) {
 			// This is index-column search.
-			$tickets = array_filter( self::tickets( false ), function( $ticket ) use ( $query ) {
+			$tickets = array_values( array_filter( self::tickets( false ), function( $ticket ) use ( $query ) {
 				$not_found = false;
 				foreach ( $query as $index => $value ) {
 					if ( ! isset( $ticket[ $index ] ) || $ticket[ $index ] != $value ) {
@@ -69,14 +69,14 @@ public static function search( $query = '', $page = 1 ) {
 					}
 				}
 				return ! $not_found;
-			} );
+			} ) );
 		} elseif ( $query ) {
 			// This is string search.
-			$tickets = array_filter( self::tickets( false ), function( $ticket ) use ( $query ) {
+			$tickets = array_values( array_filter( self::tickets( false ), function( $ticket ) use ( $query ) {
 				// Flatten array.
 				$str = implode( '', $ticket );
 				return str_contains( $str, $query );
-			} );
+			} ) );
 		} else {
 			$tickets = self::tickets( false );
 		}

From 8c11e427fbfa55e82bf78a3bc6eef2bda914edd9 Mon Sep 17 00:00:00 2001
From: fumikito <guy@hametuha.com>
Date: Tue, 13 Feb 2024 04:02:33 +0900
Subject: [PATCH 5/6] =?UTF-8?q?#8=20QR=E3=82=B3=E3=83=BC=E3=83=89=E7=94=9F?=
 =?UTF-8?q?=E6=88=90=E6=A9=9F=E8=83=BD=E5=AE=8C=E4=BA=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/WCTokyo/WpCheckin/Router.php         |  4 +-
 lib/WCTokyo/WpCheckin/Screen/Setting.php | 69 ++++++++++++++++++++++++
 2 files changed, 71 insertions(+), 2 deletions(-)

diff --git a/lib/WCTokyo/WpCheckin/Router.php b/lib/WCTokyo/WpCheckin/Router.php
index adae60c..fe38249 100644
--- a/lib/WCTokyo/WpCheckin/Router.php
+++ b/lib/WCTokyo/WpCheckin/Router.php
@@ -156,7 +156,7 @@ public function render_qr() {
 			'f' => 3,
 			'e' => 4,
 		];
-		$query = [];
+		$query  = [];
 		foreach ( $params as $name => $index ) {
 			$query[ $index ] = filter_input( INPUT_GET, $name );
 		}
@@ -168,7 +168,7 @@ public function render_qr() {
 			$url = home_url( 'checkin/?s=' . rawurlencode( $query[4] ) );
 		}
 		// Generate URL with Google Chart API.
-		$api_url     = add_query_arg( [
+		$api_url = add_query_arg( [
 			'cht' => 'qr',
 			'chs' => '300x300',
 			'chl' => $url,
diff --git a/lib/WCTokyo/WpCheckin/Screen/Setting.php b/lib/WCTokyo/WpCheckin/Screen/Setting.php
index f463472..28fc78a 100644
--- a/lib/WCTokyo/WpCheckin/Screen/Setting.php
+++ b/lib/WCTokyo/WpCheckin/Screen/Setting.php
@@ -3,7 +3,9 @@
 namespace WCTokyo\WpCheckin\Screen;
 
 
+use PHP_CodeSniffer\Generators\HTML;
 use WCTokyo\WpCheckin\Pattern\SingletonPattern;
+use WCTokyo\WpCheckin\Tickets;
 
 /**
  * Create setting screen.
@@ -22,6 +24,7 @@ protected function init() {
 		add_action( 'admin_init', [ $this, 'register_option' ] );
 		add_action( 'admin_menu', [ $this, 'add_menu' ] );
 		add_action( 'wp_ajax_' . $this->ajax_action, [ $this, 'upload_csv' ] );
+		add_action( 'admin_notices', [ $this, 'notification' ] );
 	}
 
 	/**
@@ -43,6 +46,21 @@ public function add_menu() {
 				</form>
 				<h2><?php esc_html_e( 'チケット情報', 'wp-checkin' ); ?></h2>
 				<?php $this->csv_form(); ?>
+				<h2><?php esc_html_e( 'HTMLタグ', 'wp-checkin' ); ?></h2>
+				<p>
+					<?php esc_html_e( 'WordCampサイトにおいてチケット購入者に送るメールに以下のHTMLタグを入力してください。チケットのチェックインページを開くQRコードが表示されます。', 'wp-checkin' ); ?>
+				</p>
+				<?php
+				$url  = add_query_arg( [
+					'g' => '[first_name]',
+					'f' => '[last_name]',
+					'e' => '[email]',
+				], home_url( '/checkin/qr.png' ) );
+				$text = <<<HTML
+<img src="{$url}" alt="" width="300" height="300" />
+HTML;
+				?>
+				<textarea readonly onclick="this.select();" style="width: 100%; box-sizing: border-box"><?php echo esc_textarea( $text ); ?></textarea>
 			</div>
 			<?php
 		} );
@@ -174,4 +192,55 @@ public function upload_csv() {
 			exit;
 		}
 	}
+
+	/**
+	 * メールアドレスなどについての注意書きを出す
+	 *
+	 * @return void
+	 */
+	public function notification() {
+		$success   = true;
+		$messages  = [];
+		$admin_ulr = admin_url( 'options-general.php?page=wp-checkin' );
+		// Site setting
+		$url = get_option( 'wordcamp_site_url' );
+		if ( $url ) {
+			// translators: %1$s is URL.
+			$messages[] = sprintf( __( 'WordCampサイトは<a href="%1$s">%1$s</a>です。', 'wp-checkin' ), esc_url( $url ) );
+		} else {
+			$success = false;
+			// translators: %s is URL.
+			$messages[] = sprintf( __( 'WordCampサイトのURLが<a href="%s">設定</a>されていません。', 'wp-checkin' ), $admin_ulr );
+		}
+		// Basic auth setting.
+		$user = get_option( 'wordcamp_auth_user' );
+		$pass = get_option( 'wordcamp_auth_pass' );
+		if ( $user && $pass ) {
+			$messages[] = __( 'チケットページはパスワードで保護されています。', 'wp-checkin' );
+		} else {
+			$success = false;
+			// translators: %s is URL.
+			$messages[] = sprintf( __( 'チケットページがパスワード保護されていません。パスワードを<a href="%s">設定</a>してください。', 'wp-checkin' ), $admin_ulr );
+		}
+		// Ticket imported.
+		$total = count( Tickets::tickets( false ) );
+		if ( 1 < $total ) {
+			// translators: %d is ticket count.
+			$messages[] = sprintf( __( '%d件のチケット情報が登録されています。', 'wp-checkin' ), $total );
+		} else {
+			$success = false;
+			// translators: %s is URL.
+			$messages[] = sprintf( __( 'チケットのCSが登録されていません。パスワードを<a href="%s">設定</a>してください。', 'wp-checkin' ), $admin_ulr );
+		}
+		// Output message.
+		?>
+		<div class="notice notice-<?php echo $success ? 'success' : 'error'; ?>">
+			<ol>
+				<?php foreach ( $messages as $message ) : ?>
+					<li><?php echo wp_kses_post( $message ); ?></li>
+				<?php endforeach; ?>
+			</ol>
+		</div>
+		<?php
+	}
 }

From 89e52f1b5533eb06914a30f07623cc72e1c46375 Mon Sep 17 00:00:00 2001
From: fumikito <guy@hametuha.com>
Date: Tue, 13 Feb 2024 04:04:53 +0900
Subject: [PATCH 6/6] Fix lint

---
 lib/WCTokyo/WpCheckin/Tickets.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/WCTokyo/WpCheckin/Tickets.php b/lib/WCTokyo/WpCheckin/Tickets.php
index 3f6ca01..a65e566 100644
--- a/lib/WCTokyo/WpCheckin/Tickets.php
+++ b/lib/WCTokyo/WpCheckin/Tickets.php
@@ -63,6 +63,7 @@ public static function search( $query = '', $page = 1 ) {
 			$tickets = array_values( array_filter( self::tickets( false ), function( $ticket ) use ( $query ) {
 				$not_found = false;
 				foreach ( $query as $index => $value ) {
+					// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
 					if ( ! isset( $ticket[ $index ] ) || $ticket[ $index ] != $value ) {
 						$not_found = true;
 						break;