<?php declare(strict_types=1);
namespace CityNotification;
use Shopware\Core\Framework\Plugin;
use Shopware\Core\Framework\Plugin\Context\InstallContext;
use MrShan0\PHPFirestore\FirestoreClient;
use Google\Auth\Credentials\ServiceAccountCredentials;
// expect the vendor folder on Shopware store releases
if (file_exists(dirname(__DIR__) . '/vendor/autoload.php')) {
require_once dirname(__DIR__) . '/vendor/autoload.php';
}
class CityNotification extends Plugin
{
protected $firestore;
public function executeComposerCommands(): bool
{
return true;
}
public function boot(): void
{
// 设置时区
date_default_timezone_set('Asia/Taipei');
parent::boot();
}
public function install(InstallContext $context): void
{
parent::install($context);
$merchantId = getenv('Merchant_ID') ?? 'yunlin';
$firebaseJsonFile = getenv('Firebase_Service_Account') ?: getenv('Firesbase_Service_Account') ?: '';
if ($firebaseJsonFile === '') {
throw new \RuntimeException('Missing Firebase_Service_Account (or legacy Firesbase_Service_Account). Provide service account JSON or file path.');
}
$firestoreProjectId = getenv('Firestore_Project_id') ?? '';
$firestoreApiKey = getenv('Firestore_API_Key') ?? '';
$firestoreDatabase = getenv('Firestore_database') ?? '(default)';
$this->firestore = new FirestoreClient($firestoreProjectId, $firestoreApiKey, [
'database' => $firestoreDatabase,
]);
$this->authenticateWithServiceAccount($firebaseJsonFile);
$this->initStruct($merchantId);
}
private function initStruct($merchantId)
{
// 只有创立集合时才需要
if (!$this->getDoc("notifications/$merchantId")) {
$this->firestore->addDocument("notifications", [
'isPlaceHolder' => true,
'ts' => time(),
], $merchantId);
}
if (!($this->checkExists("notifications/$merchantId/members"))) {
$this->firestore->addDocument("notifications/$merchantId/members", [
'isPlaceHolder' => true,
'ts' => time(),
]);
}
if (!($this->checkExists("notifications/$merchantId/topics"))) {
$this->firestore->addDocument("notifications/$merchantId/topics", [
'isPlaceHolder' => true,
'ts' => time(),
]);
}
if (!($this->checkExists("notifications/$merchantId/members-channel-setting"))) {
$this->firestore->addDocument("notifications/$merchantId/members-channel-setting", [
'isPlaceHolder' => true,
'ts' => time(),
]);
}
if (!($this->checkExists("notifications/$merchantId/recipients"))) {
$this->firestore->addDocument("notifications/$merchantId/recipients", [
'isPlaceHolder' => true,
'ts' => time(),
]);
}
if (!($this->checkExists("notifications/$merchantId/push-messages"))) {
$this->firestore->addDocument("notifications/$merchantId/push-messages", [
'isPlaceHolder' => true,
'ts' => time(),
]);
}
return true;
}
private function getDoc($path, $params = [])
{
try {
$doc = $this->firestore->getDocument($path);
return $doc;
} catch (\Exception $e) {
return null;
}
}
private function checkExists($collection)
{
$data = $this->firestore->listDocuments($collection, ['pageSize' => 1])['documents'];
return sizeof($data) >= 1;
}
private function authenticateWithServiceAccount(string $serviceAccount): void
{
$keyFile = $this->resolveServiceAccountKeyFile($serviceAccount);
if (empty($keyFile['client_email'])) {
throw new \RuntimeException('Invalid Firebase_Service_Account (or legacy Firesbase_Service_Account): missing client_email.');
}
$credentials = new ServiceAccountCredentials(
['https://www.googleapis.com/auth/datastore'],
$keyFile
);
$token = $this->fetchAccessTokenWithRetry($credentials);
$accessToken = $token['access_token'] ?? '';
if ($accessToken === '') {
throw new \RuntimeException('Failed to fetch Firestore access token.');
}
$this->firestore->authenticator()->setCustomToken($accessToken);
}
private function resolveServiceAccountKeyFile(string $serviceAccount): array
{
$keyFile = json_decode($serviceAccount, true);
if (is_array($keyFile)) {
return $keyFile;
}
if ($serviceAccount !== '' && file_exists($serviceAccount)) {
$keyFile = json_decode(file_get_contents($serviceAccount), true);
if (is_array($keyFile)) {
return $keyFile;
}
}
throw new \RuntimeException('Invalid Firebase_Service_Account (or legacy Firesbase_Service_Account) for Firestore client.');
}
private function fetchAccessTokenWithRetry(ServiceAccountCredentials $credentials): array
{
for ($attempt = 0; $attempt < 2; $attempt++) {
try {
$token = $credentials->fetchAuthToken();
} catch (\Exception $e) {
$token = [];
}
$accessToken = $token['access_token'] ?? '';
if ($accessToken !== '') {
return $token;
}
usleep(200000);
}
return [];
}
}