custom/plugins/notification/src/CityNotification.php line 16

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace CityNotification;
  3. use Shopware\Core\Framework\Plugin;
  4. use Shopware\Core\Framework\Plugin\Context\InstallContext;
  5. use MrShan0\PHPFirestore\FirestoreClient;
  6. use Google\Auth\Credentials\ServiceAccountCredentials;
  7. // expect the vendor folder on Shopware store releases
  8. if (file_exists(dirname(__DIR__) . '/vendor/autoload.php')) {
  9.     require_once dirname(__DIR__) . '/vendor/autoload.php';
  10. }
  11. class CityNotification extends Plugin
  12. {
  13.     protected $firestore;
  14.     public function executeComposerCommands(): bool
  15.     {        
  16.         return true;
  17.     }
  18.     public function boot(): void
  19.     {
  20.         
  21.         // 设置时区
  22.         date_default_timezone_set('Asia/Taipei');
  23.         parent::boot();
  24.     }
  25.     public function install(InstallContext $context): void
  26.     {
  27.         parent::install($context);
  28.         $merchantId getenv('Merchant_ID') ?? 'yunlin';  
  29.         $firebaseJsonFile getenv('Firebase_Service_Account') ?: getenv('Firesbase_Service_Account') ?: '';
  30.         if ($firebaseJsonFile === '') {
  31.             throw new \RuntimeException('Missing Firebase_Service_Account (or legacy Firesbase_Service_Account). Provide service account JSON or file path.');
  32.         }
  33.         $firestoreProjectId getenv('Firestore_Project_id') ?? '';
  34.         $firestoreApiKey getenv('Firestore_API_Key') ?? '';
  35.         $firestoreDatabase getenv('Firestore_database') ?? '(default)';
  36.         $this->firestore =   new FirestoreClient($firestoreProjectId$firestoreApiKey, [
  37.             'database' => $firestoreDatabase,
  38.         ]);      
  39.         $this->authenticateWithServiceAccount($firebaseJsonFile);
  40.         
  41.         $this->initStruct($merchantId);
  42.         
  43.         
  44.     }
  45.     private  function initStruct($merchantId)
  46.     {
  47.         // 只有创立集合时才需要
  48.         if (!$this->getDoc("notifications/$merchantId")) {
  49.             $this->firestore->addDocument("notifications", [
  50.                 'isPlaceHolder' => true,
  51.                 'ts' => time(),
  52.             ], $merchantId);
  53.         }
  54.         if (!($this->checkExists("notifications/$merchantId/members"))) {
  55.             $this->firestore->addDocument("notifications/$merchantId/members", [
  56.                 'isPlaceHolder' => true,
  57.                 'ts' => time(),
  58.             ]);
  59.         }
  60.         if (!($this->checkExists("notifications/$merchantId/topics"))) {
  61.             $this->firestore->addDocument("notifications/$merchantId/topics", [
  62.                 'isPlaceHolder' => true,
  63.                 'ts' => time(),
  64.             ]);
  65.         }
  66.         if (!($this->checkExists("notifications/$merchantId/members-channel-setting"))) {
  67.             $this->firestore->addDocument("notifications/$merchantId/members-channel-setting", [
  68.                 'isPlaceHolder' => true,
  69.                 'ts' => time(),
  70.             ]);
  71.         }
  72.         if (!($this->checkExists("notifications/$merchantId/recipients"))) {
  73.             $this->firestore->addDocument("notifications/$merchantId/recipients", [
  74.                 'isPlaceHolder' => true,
  75.                 'ts' => time(),
  76.             ]);
  77.         }
  78.         if (!($this->checkExists("notifications/$merchantId/push-messages"))) {
  79.             $this->firestore->addDocument("notifications/$merchantId/push-messages", [
  80.                 'isPlaceHolder' => true,
  81.                 'ts' => time(),
  82.             ]);
  83.         }
  84.         return true;
  85.     }
  86.     private function getDoc($path$params = [])
  87.     {
  88.         try {
  89.             $doc $this->firestore->getDocument($path);
  90.             return $doc;
  91.         } catch (\Exception $e) {
  92.             return null;
  93.         }
  94.     }
  95.     private function checkExists($collection)
  96.     {
  97.         $data $this->firestore->listDocuments($collection, ['pageSize' => 1])['documents'];
  98.         return sizeof($data) >= 1;
  99.     }
  100.     private function authenticateWithServiceAccount(string $serviceAccount): void
  101.     {
  102.         $keyFile $this->resolveServiceAccountKeyFile($serviceAccount);
  103.         if (empty($keyFile['client_email'])) {
  104.             throw new \RuntimeException('Invalid Firebase_Service_Account (or legacy Firesbase_Service_Account): missing client_email.');
  105.         }
  106.         $credentials = new ServiceAccountCredentials(
  107.             ['https://www.googleapis.com/auth/datastore'],
  108.             $keyFile
  109.         );
  110.         $token $this->fetchAccessTokenWithRetry($credentials);
  111.         $accessToken $token['access_token'] ?? '';
  112.         if ($accessToken === '') {
  113.             throw new \RuntimeException('Failed to fetch Firestore access token.');
  114.         }
  115.         $this->firestore->authenticator()->setCustomToken($accessToken);
  116.     }
  117.     private function resolveServiceAccountKeyFile(string $serviceAccount): array
  118.     {
  119.         $keyFile json_decode($serviceAccounttrue);
  120.         if (is_array($keyFile)) {
  121.             return $keyFile;
  122.         }
  123.         if ($serviceAccount !== '' && file_exists($serviceAccount)) {
  124.             $keyFile json_decode(file_get_contents($serviceAccount), true);
  125.             if (is_array($keyFile)) {
  126.                 return $keyFile;
  127.             }
  128.         }
  129.         throw new \RuntimeException('Invalid Firebase_Service_Account (or legacy Firesbase_Service_Account) for Firestore client.');
  130.     }
  131.     private function fetchAccessTokenWithRetry(ServiceAccountCredentials $credentials): array
  132.     {
  133.         for ($attempt 0$attempt 2$attempt++) {
  134.             try {
  135.                 $token $credentials->fetchAuthToken();
  136.             } catch (\Exception $e) {
  137.                 $token = [];
  138.             }
  139.             $accessToken $token['access_token'] ?? '';
  140.             if ($accessToken !== '') {
  141.                 return $token;
  142.             }
  143.             usleep(200000);
  144.         }
  145.         return [];
  146.     }
  147. }