commit 6db17d33bb0e26512504c37706c5f61e5892ce01 Author: jaybe Date: Wed Mar 5 04:36:37 2025 +0000 Upload files to "/" diff --git a/GADB.php b/GADB.php new file mode 100644 index 0000000..bab87f6 --- /dev/null +++ b/GADB.php @@ -0,0 +1,707 @@ +db = \Config\Database::connect('google'); + $this->zenith = \Config\Database::connect(); + } + + public function __call(string $method, array $data) + { + if (is_array($data)) { + array_walk_recursive($data, function (&$v) { + if (is_string($v)) + $v = $this->db->escape($v); + }); + } + if (!method_exists($this, $method)) + trigger_error('Call to undefined method ' . __CLASS__ . '::' . $method . '()', E_USER_ERROR); + + return call_user_func_array([$this, $method], $data); + } + + public function updateAccount($data, $is_hidden = 0) + { //1 숨김, 0 활성화 + $is_update = 0; + if ($is_hidden == '0') { + $is_update = 1; //1 업데이트, 0 제외 + } + + if(!isset($data['customerId'])) return; + $sql = "INSERT INTO aw_ad_account ( + customerId, + manageCustomer, + name, + status, + level, + canManageClients, + currencyCode, + dateTimeZone, + testAccount, + is_hidden, + is_manager, + create_time) + VALUES ( + :customerId:, + :manageCustomer:, + :name:, + :status:, + :level:, + :canManageClients:, + :currencyCode:, + :dateTimeZone:, + :testAccount:, + :is_hidden:, + :is_manager:, + NOW() + ) ON DUPLICATE KEY + UPDATE customerId = :customerId:, + manageCustomer = :manageCustomer:, + is_update = :is_update:, + name = :name:, + status = :status:, + level = :level:, + canManageClients = :canManageClients:, + currencyCode = :currencyCode:, + dateTimeZone = :dateTimeZone:, + testAccount = :testAccount:, + is_hidden = :is_hidden:, + is_manager = :is_manager:, + update_time = NOW();"; + + $params = [ + 'customerId' => $data['customerId'], + 'manageCustomer' => $data['manageCustomer'], + 'name' => $data['name'], + 'status' => $data['status'], + 'level' => $data['level'], + 'canManageClients' => (integer)$data['canManageClients'], + 'currencyCode' => $data['currencyCode'], + 'dateTimeZone' => $data['dateTimeZone'], + 'testAccount' => (integer)$data['testAccount'], + 'is_hidden' => (integer)$data['is_hidden'], + 'is_update' => (integer)$is_update, + 'is_manager' => $data['is_manager'], + ]; + $result = $this->db->query($sql, $params); + return $result; + } + + public function modifyAccountBudget($data) + { + $sql = "UPDATE aw_ad_account SET amountSpendingLimit = :amountSpendingLimit:, amountServed = :amountServed: WHERE customerId = :customerId:"; + + $params = [ + 'amountSpendingLimit' => $data['amountSpendingLimit'], + 'amountServed' => $data['amountServed'], + 'customerId' => $data['customerId'] + ]; + + $result = $this->db->query($sql, $params); + return $result; + } + + public function getAccounts($is_hidden = 0, $order = false) + { + $sql = "SELECT * FROM aw_ad_account WHERE 1"; + + if (!is_null($is_hidden)) + $sql .= " AND is_hidden = {$is_hidden}"; + if ($order) $sql .= " " . $order; + $result = $this->db_query($sql); + + return $result; + } + + public function getCampaign($id) + { + if (is_null($id)) return false; + + $sql = "SELECT * FROM aw_campaign WHERE id = {$id}"; + $result = $this->db_query($sql, true); + + return $result; + } + + public function updateAdSchedule($data = null) { + if (is_null($data)) return false; + $sql = "INSERT INTO aw_campaign_ad_schedule(`campaignId`, `id`, `status`, `dayOfWeek`, `startHour`, `startMinute`, `endHour`, `endMinute`) + VALUES(:campaignId:, :id:, :status:, :dayOfWeek:, :startHour:, :startMinute:, :endHour:, :endMinute:) + ON DUPLICATE KEY UPDATE + campaignId=:campaignId:, id=:id:, status=:status:, dayOfWeek=:dayOfWeek:, startHour=:startHour:, startMinute=:startMinute:, endHour=:endHour:, endMinute=:endMinute:"; + $params = [ + 'campaignId' => $data['campaignId'], + 'id' => $data['id'], + 'status' => $data['status'], + 'dayOfWeek' => $data['dayOfWeek'], + 'startHour' => $data['startHour'], + 'startMinute' => $data['startMinute'], + 'endHour' => $data['endHour'], + 'endMinute' => $data['endMinute'] + ]; + $result = $this->db->query($sql, $params); + + return $result; + } + + public function updateCampaign($data = null) + { + if (is_null($data)) return false; + //print_r($data);exit; + $sql = "INSERT INTO aw_campaign(customerId, id, name, status, servingStatus, startDate, endDate, budgetId, budgetName, budgetReferenceCount, budgetStatus, amount, deliveryMethod, advertisingChannelType, advertisingChannelSubType, AdServingOptimizationStatus, cpaBidAmount, create_time) + VALUES( + :customerId:, + :id:, + :name:, + :status:, + :servingStatus:, + :startDate:, + :endDate:, + :budgetId:, + :budgetName:, + :budgetReferenceCount:, + :budgetStatus:, + :budgetAmount:, + :budgetDeliveryMethod:, + :advertisingChannelType:, + :advertisingChannelSubType:, + :adServingOptimizationStatus:, + :cpaBidAmount:, + NOW()) + ON DUPLICATE KEY UPDATE + name = :name:, + status = :status:, + servingStatus = :servingStatus:, + startDate = :startDate:, + endDate = :endDate:, + budgetId = :budgetId:, + budgetName = :budgetName:, + budgetReferenceCount = :budgetReferenceCount:, + budgetStatus = :budgetStatus:, + amount = :budgetAmount:, + deliveryMethod = :budgetDeliveryMethod:, + advertisingChannelType = :advertisingChannelType:, + advertisingChannelSubType = :advertisingChannelSubType:, + AdServingOptimizationStatus = :adServingOptimizationStatus:, + cpaBidAmount = :cpaBidAmount:, + is_updating = 0, + update_time = NOW()"; + + $params = [ + 'customerId' => $data['customerId'], + 'id' => $data['id'], + 'name' => $data['name'], + 'status' => $data['status'], + 'servingStatus' => $data['servingStatus'], + 'startDate' => $data['startDate'], + 'endDate' => $data['endDate'], + 'budgetId' => (integer)$data['budgetId'], + 'budgetName' => $data['budgetName'], + 'budgetReferenceCount' => (integer)$data['budgetReferenceCount'], + 'budgetStatus' => $data['budgetStatus'], + 'budgetAmount' => (integer)$data['budgetAmount'], + 'budgetDeliveryMethod' => $data['budgetDeliveryMethod'], + 'advertisingChannelType' => $data['advertisingChannelType'], + 'advertisingChannelSubType' => $data['advertisingChannelSubType'], + 'adServingOptimizationStatus' => $data['adServingOptimizationStatus'], + 'cpaBidAmount' => (integer)$data['cpaBidAmount'] ?? 0, + ]; + + $result = $this->db->query($sql, $params); + + return $result; + } + + public function updateCampaignField($data = null) + { + if (is_null($data)) return false; + + $builder = $this->db->table('aw_campaign'); + + $updateData = []; + + if (isset($data['name'])) { + $updateData['name'] = $data['name']; + } + if (isset($data['status'])) { + $updateData['status'] = $data['status']; + } + if (isset($data['amount'])) { + $updateData['amount'] = $data['amount']; + } + if (isset($data['cpaBidAmount'])) { + $updateData['cpaBidAmount'] = $data['cpaBidAmount']; + } + + $builder->set($updateData); + $builder->where('id', $data['id']); + $result = $builder->update(); + + return $result; + } + + public function getAdGroupIdByAd($id) + { + if (is_null($id)) return false; + + $sql = "SELECT adgroupId FROM aw_ad WHERE id = {$id}"; + $result = $this->db_query($sql, true); + + return $result; + } + public function updateAdGroup($data = null) + { + if (is_null($data)) return false; + + $sql = "INSERT INTO aw_adgroup(campaignId, id, name, status, adGroupType, biddingStrategyType, cpcBidAmount, cpcBidSource, cpmBidAmount, cpmBidSource, cpaBidAmount, cpaBidSource, create_time) + VALUES( + :campaignId:, + :id:, + :name:, + :status:, + :adGroupType:, + :biddingStrategyType:, + :cpcBidAmount:, + :cpcBidSource:, + :cpmBidAmount:, + :cpmBidSource:, + :cpaBidAmount:, + '', NOW()) + ON DUPLICATE KEY UPDATE + name = :name:, + status = :status:, + adGroupType = :adGroupType:, + biddingStrategyType = :biddingStrategyType:, + cpcBidAmount = :cpcBidAmount:, + cpcBidSource = :cpcBidSource:, + cpmBidAmount = :cpmBidAmount:, + cpmBidSource = :cpmBidSource:, + cpaBidAmount = :cpaBidAmount:, + cpaBidSource = '', + update_time = NOW()"; + $params = [ + 'campaignId' => $data['campaignId'], + 'id' => $data['id'], + 'name' => $data['name'], + 'status' => $data['status'], + 'adGroupType' => $data['adGroupType'], + 'biddingStrategyType' => $data['biddingStrategyType'], + 'cpcBidAmount' => (integer)$data['cpcBidAmount'] ?? 0, + 'cpcBidSource' => $data['cpcBidSource'], + 'cpmBidAmount' => (integer)$data['cpmBidAmount'] ?? 0, + 'cpmBidSource' => $data['cpmBidSource'], + 'cpaBidAmount' => (integer)$data['cpaBidAmount'] ?? 0, + ]; + + $result = $this->db->query($sql, $params); + + return $result; + } + + public function updateAdgroupField($data = null) + { + if (is_null($data)) return false; + + $builder = $this->db->table('aw_adgroup'); + + $updateData = []; + + if (isset($data['name'])) { + $updateData['name'] = $data['name']; + } + if (isset($data['status'])) { + $updateData['status'] = $data['status']; + } + if (isset($data['cpcBidAmount'])) { + $updateData['cpcBidAmount'] = $data['cpcBidAmount']; + } + if (isset($data['cpmBidAmount'])) { + $updateData['cpmBidAmount'] = $data['cpmBidAmount']; + } + if (isset($data['cpaBidAmount'])) { + $updateData['cpaBidAmount'] = $data['cpaBidAmount']; + } + + $builder->set($updateData); + $builder->where('id', $data['id']); + $result = $builder->update(); + + return $result; + } + + public function updateAd($data = null) + { + if (is_null($data)) return false; + $this->db->transStart(); + $sql = "INSERT INTO aw_ad(adgroupId, id, name, status, reviewStatus, approvalStatus, policyTopic, code, adType, mediaType, assets, imageUrl, finalUrl, create_time) + VALUES( + :adgroupId:, + :id:, + :name:, + :status:, + :reviewStatus:, + :approvalStatus:, + :policyTopic:, + :code:, + :adType:, + :mediaType:, + :assets:, + :imageUrl:, + :finalUrl:, + NOW() + ) + ON DUPLICATE KEY UPDATE + name = :name:, + status = :status:, + reviewStatus = :reviewStatus:, + approvalStatus = :approvalStatus:, + policyTopic = :policyTopic:, + adType = :adType:, + mediaType = :mediaType:, + assets = :assets:, + imageUrl = :imageUrl:, + finalUrl = :finalUrl:, + update_time = NOW()"; + $params = [ + 'adgroupId' => $data['adgroupId'], + 'id' => $data['id'], + 'name' => $data['name'], + 'status' => $data['status'], + 'reviewStatus' => $data['reviewStatus'], + 'approvalStatus' => $data['approvalStatus'], + 'policyTopic' => $data['policyTopic'], + 'code' => $data['code'], + 'adType' => $data['adType'], + 'mediaType' => $data['mediaType'], + 'assets' => $data['assets'], + 'imageUrl' => $data['imageUrl'], + 'finalUrl' => $data['finalUrl'] + ]; + if ($result = $this->db->query($sql, $params)) { + // $this->insertReport($data); + } + $this->db->transComplete(); + return $result; + } + + public function updateAdField($data = null) + { + if (is_null($data)) return false; + + $builder = $this->db->table('aw_ad'); + + $updateData = []; + + if (isset($data['name'])) { + $updateData['name'] = $data['name']; + } + if (isset($data['status'])) { + $updateData['status'] = $data['status']; + } + + $builder->set($updateData); + $builder->where('id', $data['id']); + $result = $builder->update(); + + return $result; + } + + public function updateAsset($data) + { + if (is_null($data)) return false; + + if(empty($data['video_id'])){ + $data['video_id'] = ''; + } + + if(empty($data['url'])){ + $data['url'] = ''; + } + + $data = [ + 'id' => $this->db->escape($data['id']), + 'name' => $this->db->escape($data['name'] ?? ''), + 'type' => $this->db->escape($data['type'] ?? ''), + 'video_id' => $this->db->escape($data['video_id'] ?? ''), + 'url' => $this->db->escape($data['url'] ?? ''), + ]; + + $builder = $this->db->table('aw_asset'); + $builder->setData($data, false); + $updateTime = ['update_time' => new RawSql('NOW()')]; + $builder->updateFields($updateTime, true); + $result = $builder->upsert(); + return $result; + } + + public function getDbCount($ad_id, $date) + { + if (empty($ad_id) || empty($date)) return NULL; + $sql = "SELECT * FROM aw_db_count WHERE ad_id = '{$ad_id}' AND date = '{$date}'"; + $result = $this->db_query($sql); + if (!$result) return null; + return $result->getResultArray(); + } + + public function getAppSubscribeCount($data, $date) + { + if (empty($data['db_prefix'])) return 0; + $sql = "SELECT * FROM app_subscribe WHERE group_id = '{$data['app_id']}' AND status = 1 AND site = '{$data['site']}' AND DATE_FORMAT(reg_date, '%Y-%m-%d') = '{$date}' AND deleted = 0"; + $res = $this->zenith->query($sql); + $num_rows = $res->getNumRows(); + return $num_rows; + } + + public function getAdLeads($date, $ids = []) + { + $sql = "SELECT his.ad_id, ag.id AS adgroup_id, CONCAT('{',GROUP_CONCAT('\"',his.`hour`,'\":',his.cost),'}') AS spend_data, ad.code, ad.finalUrl, ad.name AS ad_name, ac.name AS campaign_name + FROM + `aw_ad_report_history` AS his + LEFT JOIN aw_ad AS ad ON his.ad_id = ad.id + LEFT JOIN aw_adgroup AS ag ON ad.adgroupId = ag.id + LEFT JOIN aw_campaign AS ac ON ag.campaignId = ac.id + WHERE his.date = '{$date}'"; + if(!empty($ids)){ + $sql .= " AND his.ad_id IN (".implode(',', $ids).")"; + } + $sql .= " GROUP BY his.ad_id"; + $result = $this->db_query($sql); + + return $result; + } + + public function getDbPrice($data) + { + if (empty($data['ad_id']) || empty($data['date'])){ + return NULL; + } + + $sql = "SELECT ad_id, date, db_price FROM `z_adwords`.`aw_ad_report_history` WHERE `ad_id` = '{$data['ad_id']}' AND `date` = '{$data['date']}' GROUP BY date ORDER BY hour DESC LIMIT 1;"; + $result = $this->db_query($sql); + $result = $result->getResultArray(); + + if (empty($result)){return null;} + return $result; + } + + public function getLeads($data) + { + if (empty($data['event_seq'])) return null; + $sql = "SELECT event_seq, site, date(from_unixtime(reg_timestamp)) AS date, HOUR(from_unixtime(reg_timestamp)) AS hour, count(event_seq) AS db_count + FROM `zenith`.`event_leads` + WHERE `reg_timestamp` >= unix_timestamp('{$data['date']}') + AND `status` = 1 AND `is_deleted` = 0 + AND `event_seq` = {$data['event_seq']} AND `site` = '{$data['site']}' AND DATE_FORMAT(`reg_date`, '%Y-%m-%d') = '{$data['date']}' + GROUP BY `event_seq`, `site`, HOUR(from_unixtime(reg_timestamp))"; + $result = $this->zenith->query($sql); + return $result; + } + public function insertReport($data) + { + if (empty($data)) { + return false; + } + + $row = $data; + if ($row['id']) { + $row['date'] = trim($row['date'], "'"); // 불필요한 따옴표 제거 + $data = [ + 'ad_id' => $row['id'], + 'date' => trim($row['date'], "'"), // 불필요한 따옴표 제거 + 'impressions' => $row['impressions'], + 'clicks' => $row['clicks'], + 'cost' => $row['cost'], + 'create_time' => new RawSql('NOW()') + ]; + $builder = $this->db->table('aw_ad_report'); + $builder->setData($data); + $updateTime = ['update_time' => new RawSql('NOW()')]; + $builder->updateFields($updateTime, true); + $result = $builder->upsert(); + + if ($row['date'] != date('Y-m-d')) return $result; + + $data_history = [ + 'ad_id' => $row['id'], + 'date' => trim($row['date'], "'"), // 불필요한 따옴표 제거 + 'hour' => date('H', strtotime('-10 minutes')), + 'impressions' => $row['impressions'], + 'clicks' => $row['clicks'], + 'cost' => $row['cost'], + 'create_time' => new RawSql('NOW()') + ]; + $h_builder = $this->db->table('aw_ad_report_history'); + $h_builder->setData($data_history); + $updateTime = ['update_time' => new RawSql('NOW()')]; + $h_builder->updateFields($updateTime, true); + $result = $h_builder->upsert(); + return $result; + } + } + + private function getPreviousReportData($ad_id, $date) + { + $builder = $this->db->table('aw_ad_report_history'); + $builder->select('SUM(impressions) AS impressions, SUM(clicks) AS clicks, SUM(cost) AS cost'); + $builder->where('ad_id', $ad_id); + $builder->where('date', $date); + $builder->where('hour <', 'HOUR(NOW())', false); + + $result = $builder->get(); + + if ($result === false) { + return null; + } + + return $result->getRowArray(); + } + + public function insertAdGroupReport($data) + { + if (empty($data)) { + return false; + } + + $row = $data; + if ($row['id']) { + $row['date'] = trim($row['date'], "'"); // 불필요한 따옴표 제거 + $data = [ + 'adgroup_id' => $row['id'], + 'date' => trim($row['date'], "'"), + 'hour' => $row['hour'], + 'impressions' => $row['impressions'], + 'clicks' => $row['clicks'], + 'cost' => $row['cost'], + 'create_time' => new RawSql('NOW()') + ]; + $builder = $this->db->table('aw_adgroup_report_history'); + $builder->setData($data); + $updateTime = ['update_time' => new RawSql('NOW()')]; + $builder->updateFields($updateTime, true); + $result = $builder->upsert(); + + return $result; + } + } + + public function updateAdGroupreport($adGroupId, $date, $data) + { + if (empty($adGroupId) || empty($date) || empty($data)) { + return false; + } + + for ($i = 0; $i <= 23; $i++) { + $hourData = [ + 'adgroup_id' => $adGroupId, + 'date' => $date, + 'hour' => $i, + 'db_count' => $data['count'][$i] ?? 0, + 'sales' => $data['sales'][$i] ?? 0, + 'margin' => $data['margin'][$i] ?? 0, + ]; + + $builder = $this->db->table('aw_adgroup_report_history'); + $builder->setData($hourData); + $updateTime = ['update_time' => new RawSql('NOW()')]; + $builder->updateFields($updateTime, true); + $builder->upsert(); + } + + return true; + } + + public function updateReportByHour($data) + { + if(!empty($data)){ + $row = $data; + }else{ + return false; + } + + if(isset($row['data'])){ + foreach($row['data'] as $v) { + if ($row['ad_id']) { + $data = [ + 'ad_id' => $row['ad_id'], + 'date' => $row['date'], + 'hour' => $v['hour'], + 'media' => $row['media'], + 'period' => is_numeric($row['period_ad']) ? $row['period_ad'] : 0, + 'event_seq' => $row['event_seq'], + 'site' => $row['site'], + 'db_price' => $row['db_price'], + 'db_count' => $v['count'], + 'margin' => $v['margin'], + 'sales' => $v['sales'], + ]; + $builder = $this->db->table('aw_ad_report_history'); + $builder->setData($data); + $updateTime = ['update_time' => new RawSql('NOW()')]; + $builder->updateFields($updateTime, true); + // d($builder->getCompiledUpsert()); + $builder->upsert(); + } + } + } + } + + public function updateReport($data) + { + if(!empty($data)){ + $row = $data; + }else{ + return false; + } + + if(isset($row['total'])){ + foreach($row['total'] as $v) { + if ($row['ad_id']) { + $data = [ + 'ad_id' => $row['ad_id'], + 'date' => $row['date'], + 'media' => $row['media'], + 'period' => is_numeric($row['period_ad']) ? $row['period_ad'] : 0, + 'event_seq' => $row['event_seq'], + 'site' => $row['site'], + 'db_price' => $row['db_price'], + 'db_count' => $v['count'], + 'margin' => $v['margin'], + 'sales' => $v['sales'], + ]; + $builder = $this->db->table('aw_ad_report'); + $builder->setData($data); + $updateTime = ['update_time' => new RawSql('NOW()')]; + $builder->updateFields($updateTime, true); + // d($builder->getCompiledUpsert()); + $builder->upsert(); + } + } + } + } + + public function db_query($sql, $error = false) + { + if (!$sql) return false; + $result = null; + $this->sltDB = $this->db; + $this->sltDB->query("BEGIN"); + if ($error) + $result = $this->sltDB->query($sql) or die("ERROR :" . $this->sltDB->error . ' :' . $sql); + else + $result = $this->sltDB->query($sql); + if ($result) { + //$this->tracking($sql); + } + $this->sltDB->query("COMMIT"); + return $result; + } +} diff --git a/GoogleAdsAPI.php b/GoogleAdsAPI.php new file mode 100644 index 0000000..4db3196 --- /dev/null +++ b/GoogleAdsAPI.php @@ -0,0 +1,1421 @@ +db = new GADB(); + $this->oAuth2Credential = (new OAuth2TokenBuilder())->fromFile(__DIR__ . "/".getenv("MY_SERVER_NAME")."_google_ads_php.ini")->build(); + if(getenv("MY_SERVER_NAME") == 'resta') { + $this->manageCustomerId = 9087585294; + $this->rootCustomerClients = ['7933651274','6506825344','6990566282','5982720015']; + } else if(getenv("MY_SERVER_NAME") == 'savemarketing') { + $this->manageCustomerId = 1143560207; + $this->rootCustomerClients = ['5108852466','9659489252','8025396323']; + } + $this->slack = new SlackChat(); + } + + private function setCustomerId($customerId = null) + { + $this->googleAdsClient = (new GoogleAdsClientBuilder()) + ->fromFile(__DIR__ . "/".getenv("MY_SERVER_NAME")."_google_ads_php.ini") + ->withOAuth2Credential($this->oAuth2Credential) + ->withLoginCustomerId($customerId ?? $this->manageCustomerId) + ->build(); + } + + public function getAccounts($loginCustomerId = null) + { + self::setCustomerId(); + $rootCustomerIds = []; + $rootCustomerIds = self::getAccessibleCustomers($this->googleAdsClient); + $allHierarchies = []; + $accountsWithNoInfo = []; + $step = 1; + $total = count($rootCustomerIds); + CLI::write("[".date("Y-m-d H:i:s")."]"."광고계정 수신을 시작합니다.", "light_red"); + foreach ($rootCustomerIds as $rootCustomerId) { + CLI::showProgress($step++, $total); + $customerClientToHierarchy = self::createCustomerClientToHierarchy($loginCustomerId, $rootCustomerId); + if (is_null($customerClientToHierarchy)) { + $accountsWithNoInfo[] = $rootCustomerId; + } else { + $allHierarchies += $customerClientToHierarchy; + } + } + + foreach ($allHierarchies as $rootCustomerId => $customerIdsToChildAccounts) { + $data = self::printAccountHierarchy( + self::$rootCustomerClients[$rootCustomerId], + $customerIdsToChildAccounts, + 0 + ); + $data = [ + 'customerId' => $data['customerId'], + 'manageCustomer' => $data['manageCustomer'], + 'name' => $data['name'], + 'level' => $data['level'], + 'status' => $data['status'], + 'canManageClients' => $data['canManageClients'], + 'currencyCode' => $data['currencyCode'], + 'dateTimeZone' => $data['dateTimeZone'], + 'testAccount' => $data['testAccount'], + 'is_hidden' => $data['is_hidden'], + 'is_manager' => $data['is_manager'], + ]; + $this->db->updateAccount($data); + } + } + + public function getAccountBudgets($loginCustomerId = null, $customerId = null) + { + try { + self::setCustomerId($loginCustomerId); + $googleAdsServiceClient = $this->googleAdsClient->getGoogleAdsServiceClient(); + + // 쿼리 생성 + $query = 'SELECT account_budget.status, ' + . 'account_budget.billing_setup, ' + . 'account_budget.amount_served_micros, ' + . 'account_budget.adjusted_spending_limit_micros, ' + . 'account_budget.adjusted_spending_limit_type ' + . 'FROM account_budget'; + + // SearchGoogleAdsStreamRequest 객체 생성 + $request = new SearchGoogleAdsStreamRequest([ + 'customer_id' => strval($customerId), // 고객 ID를 문자열로 변환 + 'query' => $query + ]); + // searchStream 메서드 호출 + $stream = $googleAdsServiceClient->searchStream($request); + + foreach ($stream->iterateAllElements() as $googleAdsRow) { + /** @var GoogleAdsRow $googleAdsRow */ + $accountBudget = $googleAdsRow->getAccountBudget(); + $amountServed = Helper::microToBase($accountBudget->getAmountServedMicros()); + $amountSpendingLimit = $accountBudget->getAdjustedSpendingLimitMicros() ? Helper::microToBase($accountBudget->getAdjustedSpendingLimitMicros()) : SpendingLimitType::name($accountBudget->getAdjustedSpendingLimitType()); + $data = [ + 'customerId' => $customerId, 'manageCustomer' => $loginCustomerId, 'amountServed' => $amountServed, 'amountSpendingLimit' => $amountSpendingLimit + ]; + $this->db->modifyAccountBudget($data); + } + } catch (Exception $ex) { + $msgs = [ + 'channel' => $this->slackChannel, + 'text' => "[".date("Y-m-d H:i:s")."][" . getenv('MY_SERVER_NAME') . "][구글][광고계정 예산] 수신 오류 : " . $ex->getMessage(), + ]; + $this->slack->sendMessage($msgs); + } + } + + + private static function createCustomerClientToHierarchy( + ?int $loginCustomerId, + int $rootCustomerId + ): ?array { + $oAuth2Credential = (new OAuth2TokenBuilder())->fromFile(__DIR__ . "/".getenv("MY_SERVER_NAME")."_google_ads_php.ini")->build(); + $googleAdsClient = (new GoogleAdsClientBuilder())->fromFile(__DIR__ . "/".getenv("MY_SERVER_NAME")."_google_ads_php.ini") + ->withOAuth2Credential($oAuth2Credential) + ->withLoginCustomerId($loginCustomerId ?? $rootCustomerId) + ->build(); + $googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient(); + $query = 'SELECT customer_client.client_customer, customer_client.level,' + . ' customer_client.manager, customer_client.descriptive_name,' + . ' customer_client.currency_code, customer_client.time_zone, customer_client.hidden, customer_client.status, customer_client.test_account,' + . ' customer_client.id FROM customer_client WHERE customer_client.level <= 1'; + + $rootCustomerClient = null; + $managerCustomerIdsToSearch = [$rootCustomerId]; + + $customerIdsToChildAccounts = []; + + while (!empty($managerCustomerIdsToSearch)) { + $customerIdToSearch = array_shift($managerCustomerIdsToSearch); + // SearchGoogleAdsStreamRequest 객체 생성 + $request = new SearchGoogleAdsStreamRequest([ + 'customer_id' => strval($customerIdToSearch), // 고객 ID를 문자열로 변환 + 'query' => $query + ]); + $stream = $googleAdsServiceClient->searchStream($request); + foreach ($stream->iterateAllElements() as $googleAdsRow) { + $customerClient = $googleAdsRow->getCustomerClient(); + if ($customerClient->getId() === $rootCustomerId) { + $rootCustomerClient = $customerClient; + self::$rootCustomerClients[$rootCustomerId] = $rootCustomerClient; + } + if ($customerClient->getId() === $customerIdToSearch) { + continue; + } + $customerIdsToChildAccounts[$customerIdToSearch][] = $customerClient; + if ($customerClient->getManager()) { + $alreadyVisited = array_key_exists( + $customerClient->getId(), + $customerIdsToChildAccounts + ); + if (!$alreadyVisited && $customerClient->getLevel() === 1) { + array_push($managerCustomerIdsToSearch, $customerClient->getId()); + } + } + } + } + + return is_null($rootCustomerClient) ? null + : [$rootCustomerClient->getId() => $customerIdsToChildAccounts]; + } + + private static function getAccessibleCustomers(GoogleAdsClient $googleAdsClient): array + { + $accessibleCustomerIds = []; + $customerServiceClient = $googleAdsClient->getCustomerServiceClient(); + $accessibleCustomers = $customerServiceClient->listAccessibleCustomers(new ListAccessibleCustomersRequest()); + foreach ($accessibleCustomers->getResourceNames() as $customerResourceName) { + $_customer = explode('/', $customerResourceName); + $customer = end($_customer); + $accessibleCustomerIds[] = intval($customer); + } + + return $accessibleCustomerIds; + } + + private function printAccountHierarchy( + CustomerClient $customerClient, + array $customerIdsToChildAccounts, + int $depth + ) { + $customerId = $customerClient->getId(); + if (!array_key_first($customerIdsToChildAccounts)) $rootCustomerId = $customerId; + else $rootCustomerId = array_key_first($customerIdsToChildAccounts); + //print str_repeat('-', $depth * 2); + // echo $customerId.'/'.$customerClient->getDescriptiveName().'::'.$customerClient->getClientCustomer().'::'.$customerClient->getManager().PHP_EOL; + $data = [ + 'customerId' => $customerId, + 'manageCustomer' => $rootCustomerId, + 'name' => $customerClient->getDescriptiveName(), + 'level' => $customerClient->getLevel(), + 'currencyCode' => $customerClient->getCurrencyCode(), + 'dateTimeZone' => $customerClient->getTimeZone(), + 'is_hidden' => $customerClient->getHidden() ? '1' : '0', + 'hidden' => $customerClient->getHidden(), + 'status' => CustomerStatus::name($customerClient->getStatus()), + 'testAccount' => $customerClient->getTestAccount() ? '1' : '0', + 'canManageClients' => $rootCustomerId == $customerId ? '1' : '0', + 'is_manager' => $customerClient->getManager() + ]; + //echo '
'.print_r($data,1).'
'; + if (array_key_exists($customerId, $customerIdsToChildAccounts)) { + foreach ($customerIdsToChildAccounts[$customerId] as $childAccount) { + $child = self::printAccountHierarchy($childAccount, $customerIdsToChildAccounts, $depth + 1); + $this->db->updateAccount($child); + } + } + return $data; + } + + public function getCriterions($loginCustomerId = null, $customerId = null, $campaignId = null) { + self::setCustomerId($loginCustomerId); + $googleAdsServiceClient = $this->googleAdsClient->getGoogleAdsServiceClient(); + $query = 'SELECT campaign.id, campaign_criterion.criterion_id, campaign_criterion.display_name, campaign_criterion.status, campaign_criterion.type, campaign_criterion.ad_schedule.day_of_week, campaign_criterion.ad_schedule.start_hour, campaign_criterion.ad_schedule.start_minute, campaign_criterion.ad_schedule.end_hour, campaign_criterion.ad_schedule.end_minute FROM ad_schedule_view WHERE campaign.status IN ("ENABLED","PAUSED","REMOVED") '; + if($campaignId !== null){ + $query .= "AND campaign.id = $campaignId"; + } + $query .= " ORDER BY campaign.start_date DESC"; + // SearchGoogleAdsStreamRequest 객체 생성 + $request = new SearchGoogleAdsStreamRequest([ + 'customer_id' => strval($customerId), // 고객 ID를 문자열로 변환 + 'query' => $query + ]); + // searchStream 메서드 호출 + $stream = $googleAdsServiceClient->searchStream($request); + $result = []; + $minutes = ['ZERO'=>0, 'FIFTEEN'=>15, 'THIRTY'=>30, 'FORTY_FIVE'=>45]; + foreach ($stream->iterateAllElements() as $googleAdsRow) { + $c = $googleAdsRow->getCampaign(); + $ct = $googleAdsRow->getCampaignCriterion(); + $s = $ct->getAdSchedule(); + // echo ($c->getId().":".$ct->getCriterionId().":".CriterionType::name($ct->getType()).":".$ct->getDisplayName()).PHP_EOL; + $data = [ + 'campaignId' => $c->getId(), + 'id' => $ct->getCriterionId(), + 'status' => CampaignCriterionStatus::name($ct->getStatus()), + 'dayOfWeek' => DayOfWeek::name($s->getDayOfWeek()), + 'startHour' => $s->getStartHour(), + 'startMinute' => $minutes[MinuteOfHour::name($s->getStartMinute())], + 'endHour' => $s->getEndHour(), + 'endMinute' => $minutes[MinuteOfHour::name($s->getEndMinute())], + ]; + if ($this->db->updateAdSchedule($data)) + $result[] = $data; + } + return $result; + } + + public function getCampaigns($loginCustomerId = null, $customerId = null, $campaignId = null) + { + try { + self::setCustomerId($loginCustomerId); + $googleAdsServiceClient = $this->googleAdsClient->getGoogleAdsServiceClient(); + // Creates a query that retrieves all campaigns. + $query = 'SELECT customer.id, campaign.id, campaign.name, campaign.status, campaign.serving_status, campaign.start_date, campaign.end_date, campaign.advertising_channel_type, campaign.advertising_channel_sub_type, campaign.ad_serving_optimization_status, campaign.base_campaign, campaign_budget.id, campaign_budget.name, campaign_budget.reference_count, campaign_budget.status, campaign_budget.amount_micros, campaign_budget.delivery_method, campaign.target_cpa.target_cpa_micros, campaign.frequency_caps FROM campaign WHERE campaign.status IN ("ENABLED","PAUSED","REMOVED") '; + if($campaignId !== null){ + $query .= "AND campaign.id = $campaignId"; + } + $query .= " ORDER BY campaign.start_date DESC"; + // SearchGoogleAdsStreamRequest 객체 생성 + $request = new SearchGoogleAdsStreamRequest([ + 'customer_id' => strval($customerId), // 고객 ID를 문자열로 변환 + 'query' => $query + ]); + // searchStream 메서드 호출 + $stream = $googleAdsServiceClient->searchStream($request); + $result = []; + foreach ($stream->iterateAllElements() as $googleAdsRow) { + $c = $googleAdsRow->getCampaign(); + $targetCpa = $c->getTargetCpa(); + $cpaBidAmount = 0; + if(!empty($targetCpa)){ + $cpaBidAmount = $targetCpa->getTargetCpaMicros() / 1000000; + } + + $budget = $googleAdsRow->getCampaignBudget(); + $advertisingChannelType = ($c->getAdvertisingChannelType() <= 11) ? AdvertisingChannelType::name($c->getAdvertisingChannelType()) : $c->getAdvertisingChannelType(); + $data = [ + 'customerId' => $googleAdsRow->getCustomer()->getId(), + 'id' => $c->getId(), + 'name' => $c->getName(), + 'status' => CampaignStatus::name($c->getStatus()), 'servingStatus' => CampaignServingStatus::name($c->getServingStatus()), + 'startDate' => $c->getStartDate(), + 'endDate' => $c->getEndDate(), + 'advertisingChannelType' => $advertisingChannelType, + 'advertisingChannelSubType' => AdvertisingChannelSubType::name($c->getAdvertisingChannelSubType()), + 'adServingOptimizationStatus' => AdServingOptimizationStatus::name($c->getAdServingOptimizationStatus()), + 'baseCampaign' => $c->getBaseCampaign(), + 'budgetId' => $budget->getId(), + 'budgetName' => $budget->getName(), + 'budgetReferenceCount' => $budget->getReferenceCount(), + 'budgetStatus' => BudgetStatus::name($budget->getStatus()), + 'budgetAmount' => ($budget->getAmountMicros() / 1000000), + 'budgetDeliveryMethod' => BudgetDeliveryMethod::name($budget->getDeliveryMethod()), + 'cpaBidAmount' => $cpaBidAmount + //,'targetCpa' => $c->getTargetCpa() + ]; + //echo '
'.print_r($data,1).'
'; + if ($this->db->updateCampaign($data)) + $result[] = $data; + } + return $result; + } catch (Exception $ex) { + $msgs = [ + 'channel' => $this->slackChannel, + 'text' => "[".date("Y-m-d H:i:s")."][" . getenv('MY_SERVER_NAME') . "][구글][광고캠페인] 수신 오류 : " . $ex->getMessage(), + ]; + $this->slack->sendMessage($msgs); + } + } + + public function updateCampaign($customerId = null, $campaignId = null, $param = null) + { + $account = $this->db->getAccounts(0, "AND customerId = {$customerId}"); + $account = $account->getRowArray(); + self::setCustomerId($account['manageCustomer']); + + $campaignServiceClient = $this->googleAdsClient->getCampaignServiceClient(); + $data = [ + 'resource_name' => ResourceNames::forCampaign($customerId, $campaignId), + ]; + + if(isset($param['status'])){ + if($param['status'] == 'ENABLED'){ + $data['status'] = CampaignStatus::ENABLED; + }else{ + $data['status'] = CampaignStatus::PAUSED; + } + } + + if(isset($param['name'])){ + $data['name'] = $param['name']; + } + + if(isset($param['cpaBidAmount'])){ + $data['target_cpa'] = new TargetCpa(['target_cpa_micros' => intval($param['cpaBidAmount']) * 1000000]); + } + + $campaign = new Campaign($data); + $campaignOperation = new CampaignOperation(); + $campaignOperation->setUpdate($campaign); + $campaignOperation->setUpdateMask(FieldMasks::allSetFieldsOf($campaign)); + + $response = $campaignServiceClient->mutateCampaigns( + $customerId, + [$campaignOperation], + ['responseContentType' => ResponseContentType::MUTABLE_RESOURCE] + //캠페인 결과값도 반환 //상수 RESOURCE_NAME_ONLY - 리소스 네임만 + ); + $updatedCampaign = $response->getResults()[0]; + $campaignInfo = $updatedCampaign->getCampaign(); + + if(!empty($campaignInfo)){ + $setData = [ + 'id' => $campaignInfo->getId(), + ]; + + if(isset($data['status'])){ + $setData['status'] = $campaignInfo->getStatus() == 2 ? 'ENABLED' : 'PAUSED'; + } + + if(isset($data['name'])){ + $setData['name'] = $campaignInfo->getName(); + } + + if(isset($data['target_cpa'])){ + if(!empty($campaignInfo->getTargetCpa()->getTargetCpaMicros())){ + $setData['cpaBidAmount'] = $campaignInfo->getTargetCpa()->getTargetCpaMicros() / 1000000; + }else{ + $setData['cpaBidAmount'] = 0; + } + } + $this->db->updateCampaignField($setData); + return $setData; + }; + } + + public function getCampaignStatusBudget($customerId, $campaignId) + { + $account = $this->db->getAccounts(0, "AND customerId = {$customerId}"); + $account = $account->getRowArray(); + self::setCustomerId($account['manageCustomer']); + $googleAdsServiceClient = $this->googleAdsClient->getGoogleAdsServiceClient(); + $query = "SELECT campaign.id, campaign.status, campaign_budget.amount_micros FROM campaign WHERE campaign.status IN ('ENABLED','PAUSED','REMOVED') AND campaign.id = $campaignId"; + // SearchGoogleAdsStreamRequest 객체 생성 + $request = new SearchGoogleAdsStreamRequest([ + 'customer_id' => strval($customerId), // 고객 ID를 문자열로 변환 + 'query' => $query + ]); + // searchStream 메서드 호출 + $stream = $googleAdsServiceClient->searchStream($request); + foreach ($stream->iterateAllElements() as $googleAdsRow) { + $c = $googleAdsRow->getCampaign(); + $budget = $googleAdsRow->getCampaignBudget(); + $data = [ + 'id' => $c->getId(), + 'status' => CampaignStatus::name($c->getStatus()), + 'budget' => ($budget->getAmountMicros() / 1000000), + ]; + } + return $data; + } + + public function updateCampaignBudget($customerId = null, $campaignId = null, $param = null) + { + $account = $this->db->getAccounts(0, "AND customerId = {$customerId}"); + $account = $account->getRowArray(); + self::setCustomerId($account['manageCustomer']); + + $campaign = $this->db->getCampaign($campaignId); + $campaign = $campaign->getRowArray(); + + $campaignServiceClient = $this->googleAdsClient->getCampaignBudgetServiceClient(); + + $param['budget'] *= 1000000; + $data = [ + 'resource_name' => ResourceNames::forCampaignBudget($customerId, $campaign['budgetId']), + 'amount_micros' => intval($param['budget']), + ]; + + $campaignBudget = new CampaignBudget($data); + $campaignOperation = new CampaignBudgetOperation(); + $campaignOperation->setUpdate($campaignBudget); + $campaignOperation->setUpdateMask(FieldMasks::allSetFieldsOf($campaignBudget)); + + $response = $campaignServiceClient->mutateCampaignBudgets( + $customerId, + [$campaignOperation], + ['responseContentType' => ResponseContentType::MUTABLE_RESOURCE] + //캠페인 결과값도 반환 //상수 RESOURCE_NAME_ONLY - 리소스 네임만 + ); + + $updatedCampaign = $response->getResults()[0]; + $amount = $updatedCampaign->getCampaignBudget()->getAmountMicros(); + $amount = $amount / 1000000; + if(!empty($updatedCampaign)){ + $setData = [ + 'id' => $campaign['id'], + 'amount' => $amount, + ]; + + $this->db->updateCampaignField($setData); + return $setData['id']; + }else{ + return false; + }; + } + + private static function convertToString($value) + { + if (is_null($value)) { + return NULL; + } + if (gettype($value) === 'boolean') { + return $value ? 'true' : 'false'; + } elseif (gettype($value) === 'object' && get_class($value) === RepeatedField::class) { + return json_encode(iterator_to_array($value->getIterator())); + } else { + //return ''; + return strval($value); + } + } + + public function getAdGroups($loginCustomerId = null, $customerId = null, $campaignId = null) + { + try { + self::setCustomerId($loginCustomerId); + $googleAdsServiceClient = $this->googleAdsClient->getGoogleAdsServiceClient(); + $query = 'SELECT campaign.id, ad_group.id, ad_group.name, ad_group.status, ad_group.type, bidding_strategy.id, campaign.bidding_strategy_type, ad_group.cpc_bid_micros, ad_group.cpm_bid_micros, ad_group.target_cpa_micros FROM ad_group WHERE ad_group.status IN ("ENABLED","PAUSED","REMOVED") '; + if ($campaignId !== null) { + $query .= " AND campaign.id = $campaignId"; + } + // SearchGoogleAdsStreamRequest 객체 생성 + $request = new SearchGoogleAdsStreamRequest([ + 'customer_id' => strval($customerId), // 고객 ID를 문자열로 변환 + 'query' => $query + ]); + // searchStream 메서드 호출 + $stream = $googleAdsServiceClient->searchStream($request); + $result = []; + foreach ($stream->iterateAllElements() as $googleAdsRow) { + $g = $googleAdsRow->getAdGroup(); + $c = $googleAdsRow->getCampaign(); + $biddingStrategyType = ''; + if ($c->getBiddingStrategyType()) { + $biddingStrategyType = BiddingStrategyType::name($c->getBiddingStrategyType()); + } + + //$bid = $googleAdsRow->getBiddingStrategy(); + $cpcBidAmount = $cpmBidAmount = $cpaBidAmount = 0; + if(!empty($g->getCpcBidMicros())){ + $cpcBidAmount = $g->getCpcBidMicros() / 1000000; + } + + if(!empty($g->getCpmBidMicros())){ + $cpmBidAmount = $g->getCpmBidMicros() / 1000000; + } + + if(!empty($g->getTargetCpaMicros())){ + $cpaBidAmount = $g->getTargetCpaMicros() / 1000000; + } + + $data = [ + 'campaignId' => $c->getId(), + 'id' => $g->getId(), + 'name' => $g->getName(), + 'status' => AdGroupStatus::name($g->getStatus()), + 'adGroupType' => AdGroupType::name($g->getType()), + 'biddingStrategyType' => $biddingStrategyType, + 'cpcBidAmount' => $cpcBidAmount, + 'cpcBidSource' => '', + 'cpmBidAmount' => $cpmBidAmount, + 'cpmBidSource' => '', + 'cpaBidAmount' => $cpaBidAmount, + //'cpaBidSource' => $g->getEffectiveTargetCpaSource() ?? '' + ]; + + /* if(!empty($bid)){ + $data['cpcBidSource'] = $bid->getEffectiveCpcBidSource() ?? ''; + $data['cpmBidSource'] = $bid->getEffectiveCpmBidSource() ?? ''; + } */ + + if ($this->db->updateAdGroup($data)) + $result[] = $data; + } + return $result; + } catch (Exception $ex) { + $msgs = [ + 'channel' => $this->slackChannel, + 'text' => "[".date("Y-m-d H:i:s")."][" . getenv('MY_SERVER_NAME') . "][구글][광고그룹] 수신 오류 : " . $ex->getMessage(), + ]; + $this->slack->sendMessage($msgs); + } + } + + public function getAdgroupStatus($customerId, $adgroupId) + { + $account = $this->db->getAccounts(0, "AND customerId = {$customerId}"); + $account = $account->getRowArray(); + self::setCustomerId($account['manageCustomer']); + $googleAdsServiceClient = $this->googleAdsClient->getGoogleAdsServiceClient(); + $query = "SELECT ad_group.id, ad_group.status FROM ad_group WHERE ad_group.status IN ('ENABLED','PAUSED','REMOVED') AND ad_group.id = $adgroupId"; + // SearchGoogleAdsStreamRequest 객체 생성 + $request = new SearchGoogleAdsStreamRequest([ + 'customer_id' => strval($customerId), // 고객 ID를 문자열로 변환 + 'query' => $query + ]); + // searchStream 메서드 호출 + $stream = $googleAdsServiceClient->searchStream($request); + foreach ($stream->iterateAllElements() as $googleAdsRow) { + $g = $googleAdsRow->getAdGroup(); + $data = [ + 'id' => $g->getId(), + 'status' => AdGroupStatus::name($g->getStatus()), + ]; + } + return $data; + } + + public function updateAdGroup($customerId = null, $adsetId = null, $param = null) + { + $account = $this->db->getAccounts(0, "AND customerId = {$customerId}"); + $account = $account->getRowArray(); + self::setCustomerId($account['manageCustomer']); + + $adGroupServiceClient = $this->googleAdsClient->getAdGroupServiceClient(); + $data = [ + 'resource_name' => ResourceNames::forAdGroup($customerId, $adsetId), + ]; + + if(isset($param['status'])){ + if($param['status'] == 'ENABLED'){ + $data['status'] = AdGroupStatus::ENABLED; + }else{ + $data['status'] = AdGroupStatus::PAUSED; + } + } + + if(isset($param['name'])){ + $data['name'] = $param['name']; + } + + if(isset($param['cpcBidAmount'])){ + $data['cpc_bid_micros'] = intval($param['cpcBidAmount']) * 1000000; + } + + if(isset($param['cpmBidAmount'])){ + $data['cpm_bid_micros'] = intval($param['cpmBidAmount']) * 1000000; + } + + if(isset($param['cpaBidAmount'])){ + $data['target_cpa_micros'] = intval($param['cpaBidAmount']) * 1000000; + } + + $adGroup = new AdGroup($data); + + $adGroupOperation = new AdGroupOperation(); + $adGroupOperation->setUpdate($adGroup); + $adGroupOperation->setUpdateMask(FieldMasks::allSetFieldsOf($adGroup)); + + $response = $adGroupServiceClient ->mutateAdGroups( + $customerId, + [$adGroupOperation], + ['responseContentType' => ResponseContentType::MUTABLE_RESOURCE] + ); + + $updatedAdGroup = $response->getResults()[0]; + $adGroupInfo = $updatedAdGroup ->getAdGroup(); + + if(!empty($adGroupInfo)){ + $setData = [ + 'id' => $adGroupInfo->getId(), + ]; + + if(isset($data['status'])){ + $setData['status'] = $adGroupInfo->getStatus() == 2 ? 'ENABLED' : 'PAUSED'; + } + + if(isset($data['name'])){ + $setData['name'] = $adGroupInfo->getName(); + } + + if(isset($data['cpc_bid_micros'])){ + if(!empty($adGroupInfo->getCpcBidMicros())){ + $setData['cpcBidAmount'] = $adGroupInfo->getCpcBidMicros() / 1000000; + }else{ + $setData['cpcBidAmount'] = 0; + } + } + + if(isset($data['cpm_bid_micros'])){ + if(!empty($adGroupInfo->getCpmBidMicros())){ + $setData['cpmBidAmount'] = $adGroupInfo->getCpmBidMicros() / 1000000; + }else{ + $setData['cpmBidAmount'] = 0; + } + } + + if(isset($data['target_cpa_micros'])){ + if(!empty($adGroupInfo->getTargetCpaMicros())){ + $setData['cpaBidAmount'] = $adGroupInfo->getTargetCpaMicros() / 1000000; + }else{ + $setData['cpaBidAmount'] = 0; + } + } + + $this->db->updateAdgroupField($setData); + return $setData; + }else{ + return false; + }; + } + + public function getReports($date = null, $edate = null, $accounts = null) + { + try { + $startTime = microtime(true); // 시작 시간 기록 + + if (is_null($accounts)) { + $accounts = $this->db->getAccounts(0, "AND status = 'ENABLED'"); + $lists = $accounts->getResultArray(); + } else { + $lists = $accounts; + } + $total = count($lists); + $step = 1; + CLI::write("[".date("Y-m-d H:i:s")."]"."보고서 업데이트를 시작합니다.", "light_red"); + $result = []; + foreach ($lists as $account) { + CLI::showProgress($step++, $total); + if(isset($account['is_manager']) && $account['is_manager'] == 1) continue; + self::setCustomerId($account['manageCustomer']); + $googleAdsServiceClient = $this->googleAdsClient->getGoogleAdsServiceClient(); + if ($date == null) $date = date('Y-m-d'); + if ($edate == null) $edate = date('Y-m-d'); + + $query = 'SELECT ad_group_ad.ad.id, metrics.clicks, metrics.impressions, metrics.cost_micros, segments.date FROM ad_group_ad WHERE customer.id = '.$account['customerId'].' AND ad_group_ad.status IN ("ENABLED","PAUSED","REMOVED") AND segments.date >= "' . $date . '" AND segments.date <= "' . $edate . '"'; + // SearchGoogleAdsStreamRequest 객체 생성 + $request = new SearchGoogleAdsStreamRequest([ + 'customer_id' => strval($account['customerId']), // 고객 ID를 문자열로 변환 + 'query' => $query + ]); + // searchStream 메서드 호출 + $stream = $googleAdsServiceClient->searchStream($request); + foreach ($stream->iterateAllElements() as $googleAdsRow) { + $d = $googleAdsRow->getAdGroupAd(); + $s = $googleAdsRow->getSegments(); + $metric = $googleAdsRow->getMetrics(); + $data = [ + 'id' => $d->getAd()->getId() ? $d->getAd()->getId() : "", + 'date' => $s->getDate(), + 'clicks' => $metric->getClicks() ? $metric->getClicks() : 0, + 'impressions' => $metric->getImpressions(), + 'cost' => round($metric->getCostMicros() / 1000000) + ]; + if ($this->db->insertReport($data)) + $result[] = $data; + } + + // 시간대별 광고그룹 metrics 수신 + $hourlyQuery = 'SELECT ad_group.id, metrics.clicks, metrics.impressions, metrics.cost_micros, segments.date, segments.hour FROM ad_group WHERE customer.id = '.$account['customerId'].' AND ad_group.status IN ("ENABLED","PAUSED","REMOVED") AND segments.date >= "' . $date . '" AND segments.date <= "' . $edate . '"'; + $hourlyRequest = new SearchGoogleAdsStreamRequest([ + 'customer_id' => strval($account['customerId']), // 고객 ID를 문자열로 변환 + 'query' => $hourlyQuery + ]); + $hourlyStream = $googleAdsServiceClient->searchStream($hourlyRequest); + foreach ($hourlyStream->iterateAllElements() as $googleAdsRow) { + $g = $googleAdsRow->getAdGroup(); + $s = $googleAdsRow->getSegments(); + $metric = $googleAdsRow->getMetrics(); + $hourlyData = [ + 'id' => $g->getId(), + 'date' => $s->getDate(), + 'hour' => $s->getHour(), + 'clicks' => $metric->getClicks() ? $metric->getClicks() : 0, + 'impressions' => $metric->getImpressions(), + 'cost' => round($metric->getCostMicros() / 1000000) + ]; + if ($this->db->insertAdGroupReport($hourlyData)) + $result[] = $hourlyData; + } + } + $endTime = microtime(true); // 종료 시간 기록 + $executionTime = round($endTime - $startTime, 2); // 실행 시간 계산 + + $msgs = [ + 'channel' => $this->slackChannel, + 'text' => "[".date("Y-m-d H:i:s")."][" . getenv('MY_SERVER_NAME') . "][구글][보고서] {$date}~{$edate} 수신 완료 (실행 시간: {$executionTime}초)", + ]; + $this->slack->sendMessage($msgs); + return $result; + } catch (Exception $ex) { + $msgs = [ + 'channel' => $this->slackChannel, + 'text' => "[".date("Y-m-d H:i:s")."][" . getenv('MY_SERVER_NAME') . "][구글][보고서] {$date}~{$edate} 수신 오류 : " . $ex->getMessage(), + ]; + $this->slack->sendMessage($msgs); + } + } + + public function getAds($loginCustomerId = null, $customerId = null, $adGroupId = null, $date = null) + { + try { + self::setCustomerId($loginCustomerId); + $googleAdsServiceClient = $this->googleAdsClient->getGoogleAdsServiceClient(); + if ($date == null) + $date = date('Y-m-d'); + + $query = 'SELECT ad_group_ad.ad.responsive_display_ad.business_name, ad_group.id, ad_group_ad.ad.id, ad_group_ad.ad.name, ad_group_ad.status, ad_group_ad.policy_summary.policy_topic_entries, ad_group_ad.policy_summary.review_status, ad_group_ad.policy_summary.approval_status, ad_group_ad.ad.type, ad_group_ad.ad.image_ad.image_url, ad_group_ad.ad.final_urls, ad_group_ad.ad.url_collections, ad_group_ad.ad.video_responsive_ad.call_to_actions, ad_group_ad.ad.image_ad.mime_type, ad_group_ad.ad.responsive_display_ad.marketing_images, ad_group_ad.ad.video_responsive_ad.videos FROM ad_group_ad WHERE ad_group_ad.status IN ("ENABLED","PAUSED","REMOVED") '; + + if ($adGroupId !== null) { + $query .= " AND ad_group.id = $adGroupId"; + } + + //echo $query; + // SearchGoogleAdsStreamRequest 객체 생성 + $request = new SearchGoogleAdsStreamRequest([ + 'customer_id' => strval($customerId), // 고객 ID를 문자열로 변환 + 'query' => $query + ]); + // searchStream 메서드 호출 + $stream = $googleAdsServiceClient->searchStream($request); + $result = []; + foreach ($stream->iterateAllElements() as $googleAdsRow) { + $g = $googleAdsRow->getAdGroup(); + $d = $googleAdsRow->getAdGroupAd(); + // $metric = $googleAdsRow->getMetrics(); + $imgType = ""; + $imgUrl = ""; + $v = []; + $assets = ""; + if (!is_null($d->getAd()->getImageAd())) { + $imgType = MimeType::name($d->getAd()->getImageAd()->getMimeType()); + $imgUrl = $d->getAd()->getImageAd()->getImageUrl(); + } + $finalUrl = $d->getAd()->getFinalUrls()->count() ? $d->getAd()->getFinalUrls()[0] : ''; + $adType = $d->getAd()->getType() < 35 ? AdType::name($d->getAd()->getType()) : $d->getAd()->getType(); + if ($adType == "RESPONSIVE_DISPLAY_AD") { + foreach ($d->getAd()->getResponsiveDisplayAd()->getMarketingImages() as $row) { + $v[] = array_pop(explode('/', $row->getAsset())); + } + $assets = implode(',', $v); + } + if ($adType == "VIDEO_RESPONSIVE_AD") { + foreach ($d->getAd()->getVideoResponsiveAd()->getVideos() as $row) { + $v[] = array_pop(explode('/', $row->getAsset())); + } + $assets = implode(',', $v); + } + if ($finalUrl) { + $url = parse_url($finalUrl); + $url = array_merge(['evt_no' => @array_pop(explode('/', $url['path'])), 'group' => ''], $url); + if (preg_match('/event\./', $url['host']) && preg_match('/^[0-9]+$/', $url['evt_no'])) + $url['group'] = 'ger'; + else if (preg_match('/^app_[0-9]+$/', $url['evt_no'])) + $url['group'] = 'ghr'; + //print_r($url); + //echo '
'; + } + + $status = AdGroupAdStatus::name($d->getStatus()); + $reviewStatus = PolicyReviewStatus::name($d->getPolicySummary()->getReviewStatus()); + $approvalStatus = PolicyApprovalStatus::name($d->getPolicySummary()->getApprovalStatus()); + $topics = []; + $topic = ''; + foreach($d->getPolicySummary()->getPolicyTopicEntries() as $entry) { + $topics[] = $entry->getTopic(); + } + if(count($topics)) $topic = implode(',', $topics); + $data = [ + 'adgroupId' => $g->getId(), + 'id' => $d->getAd()->getId() ? $d->getAd()->getId() : "", + 'name' => $d->getAd()->getName() ? $d->getAd()->getName() : "", + 'status' => $status ? $status : "", + 'reviewStatus' => $reviewStatus ? $reviewStatus : "", + 'approvalStatus' => $approvalStatus ? $approvalStatus : "", + 'policyTopic' => $topic, + 'code' => "", + 'adType' => $adType ? $adType : "", + 'mediaType' => $imgType ? $imgType : "", + 'imageUrl' => $imgUrl ? $imgUrl : "", + 'assets' => $assets ? $assets : "", + 'finalUrl' => $finalUrl ? $finalUrl : "", + 'date' => $date ? $date : "", + // 'clicks' => $metric->getClicks() ? $metric->getClicks() : "", + // 'impressions' => $metric->getImpressions(), + // 'cost' => round($metric->getCostMicros() / 1000000) + ]; + + //echo '
'.print_r($data,1).'
'; + if ($this->db->updateAd($data)) + $result[] = $data; + } + + return $result; + } catch (Exception $ex) { + $msgs = [ + 'channel' => $this->slackChannel, + 'text' => "[".date("Y-m-d H:i:s")."][" . getenv('MY_SERVER_NAME') . "][구글][광고그룹] 수신 오류 : " . $ex->getMessage(), + ]; + $this->slack->sendMessage($msgs); + } + } + + public function getAdStatus($customerId, $adId) + { + $account = $this->db->getAccounts(0, "AND customerId = {$customerId}"); + $account = $account->getRowArray(); + self::setCustomerId($account['manageCustomer']); + $googleAdsServiceClient = $this->googleAdsClient->getGoogleAdsServiceClient(); + $query = "SELECT ad_group_ad.ad.id, ad_group_ad.status FROM ad_group_ad WHERE ad_group_ad.status IN ('ENABLED','PAUSED','REMOVED') AND ad_group_ad.ad.id = $adId"; + // SearchGoogleAdsStreamRequest 객체 생성 + $request = new SearchGoogleAdsStreamRequest([ + 'customer_id' => strval($customerId), // 고객 ID를 문자열로 변환 + 'query' => $query + ]); + // searchStream 메서드 호출 + $stream = $googleAdsServiceClient->searchStream($request); + foreach ($stream->iterateAllElements() as $googleAdsRow) { + $d = $googleAdsRow->getAdGroupAd(); + $data = [ + 'id' => $d->getAd()->getId(), + 'status' => AdGroupAdStatus::name($d->getStatus()), + ]; + } + return $data; + } + + public function updateAdGroupAd($customerId = null, $adGroupId = null, $adId = null, $param = null) + { + $account = $this->db->getAccounts(0, "AND customerId = {$customerId}"); + $account = $account->getRowArray(); + self::setCustomerId($account['manageCustomer']); + + $adGroupId = $this->db->getAdGroupIdByAd($adId); + $adGroupId = $adGroupId->getRow()->adgroupId; + $googleAdsClient = $this->googleAdsClient->getAdGroupAdServiceClient(); + try { + $data = [ + 'resource_name' => ResourceNames::forAdGroupAd($customerId,$adGroupId, $adId), + ]; + + if(isset($param['status'])){ + if($param['status'] == 'ENABLED'){ + $data['status'] = AdGroupAdStatus::ENABLED; + }else{ + $data['status'] = AdGroupAdStatus::PAUSED; + } + } + + $adGroupAd = new AdGroupAd($data); + + $adGroupAdOperation = new AdGroupAdOperation(); + $adGroupAdOperation->setUpdate($adGroupAd); + $adGroupAdOperation->setUpdateMask(FieldMasks::allSetFieldsOf($adGroupAd)); + + $request = new MutateAdGroupAdsRequest([ + 'customer_id' => $customerId, + 'operations' => [$adGroupAdOperation], + 'response_content_type' => ResponseContentType::MUTABLE_RESOURCE + ]); + + $response = $googleAdsClient->mutateAdGroupAds($request); + + $updatedAdGroupAd = $response->getResults()[0]; + $adGroupAdInfo = $updatedAdGroupAd->getAdGroupAd(); + if(!empty($adGroupAdInfo)){ + $setData = [ + 'id' => $adGroupAdInfo->getAd()->getId(), + ]; + + if(isset($data['status'])){ + $setData['status'] = $adGroupAdInfo->getStatus() == 2 ? 'ENABLED' : 'PAUSED'; + } + + $this->db->updateAdField($setData); + return $setData; + }else{ + return false; + } + } catch (ApiException $e) { + return [ + 'success' => false, + 'response' => false, + 'message' => $e->getMessage(), + 'details' => $e->getBasicMessage() + ]; + } + } + + public function updateAd($customerId = null, $adGroupId = null, $adId = null, $param = null) + { + $account = $this->db->getAccounts(0, "AND customerId = {$customerId}"); + $account = $account->getRowArray(); + self::setCustomerId($account['manageCustomer']); + + $adGroupId = $this->db->getAdGroupIdByAd($adId); + $adGroupId = $adGroupId->getRow()->adgroupId; + $googleAdsClient = $this->googleAdsClient->getAdServiceClient(); + $data = [ + 'resource_name' => ResourceNames::forAd($customerId, $adId), + ]; + + if(isset($param['name'])){ + $data['name'] = $param['name']; + } + //dd($googleAdsClient->getAd($data['resource_name'])); + $ad = new Ad($data); + + $adOperation = new AdOperation(); + $adOperation->setUpdate($ad); + $adOperation->setUpdateMask(FieldMasks::allSetFieldsOf($ad)); + + $response = $googleAdsClient ->mutateAds( + $customerId, + [$adOperation], + ['responseContentType' => ResponseContentType::MUTABLE_RESOURCE] + ); + + $updatedAd = $response->getResults()[0]; + $adInfo = $updatedAd->getAd(); + if(!empty($adInfo)){ + $setData = [ + 'id' => $adInfo->getId(), + ]; + + if(isset($data['name'])){ + $setData['name'] = $adInfo->getName(); + } + + $this->db->updateAdField($setData); + return $setData; + }; + } + + public function getAsset($loginCustomerId = null, $customerId = null) + { + try { + self::setCustomerId($loginCustomerId); + $googleAdsServiceClient = $this->googleAdsClient->getGoogleAdsServiceClient(); + // Creates a query that will retrieve all image assets. + $query = "SELECT asset.id, asset.name, asset.type, " . + "asset.youtube_video_asset.youtube_video_id, " . + "asset.youtube_video_asset.youtube_video_title, " . + "asset.text_asset.text, " . + "asset.image_asset.full_size.url " . + "FROM asset"; + // SearchGoogleAdsStreamRequest 객체 생성 + $request = new SearchGoogleAdsStreamRequest([ + 'customer_id' => strval($customerId), // 고객 ID를 문자열로 변환 + 'query' => $query + ]); + // searchStream 메서드 호출 + $stream = $googleAdsServiceClient->searchStream($request); + + // Iterates over all rows in all pages and prints the requested field values for the image + // asset in each row. + $result = []; + foreach ($stream->iterateAllElements() as $googleAdsRow) { + $asset = $googleAdsRow->getAsset(); + $type = AssetType::name($asset->getType()); + $data = [ + 'id' => $asset->getId(), + 'name' => $asset->getName(), + 'type' => $type + ]; + $tData = []; + if ($type == 'IMAGE') { + $tData = [ + 'url' => $asset->getImageAsset()->getFullSize()->getUrl() ?? '' + ]; + } else if ($type == 'YOUTUBE_VIDEO') { + if(!empty($asset->getYoutubeVideoAsset())){ + if (method_exists($asset->getYoutubeVideoAsset(), 'getYoutubeVideoId')) { + $tData = [ + 'video_id' => $asset->getYoutubeVideoAsset()->getYoutubeVideoId() ?? '', + 'name' => $asset->getYoutubeVideoAsset()->getYoutubeVideoTitle() ?? '', + 'url' => 'http://i4.ytimg.com/vi/' . $asset->getYoutubeVideoAsset()->getYoutubeVideoId() . '/mqdefault.jpg' ?? '' + ]; + } + } + } else if ($type == 'TEXT') { + $tData = [ + 'name' => $asset->getTextAsset()->getText() ?? '' + ]; + } + $data = array_merge($data, $tData); + $dbResult = $this->db->updateAsset($data); + if(!empty($dbResult)){ + $result[] = $data; + } + } + return $result; + } catch (Exception $ex) { + $msgs = [ + 'channel' => $this->slackChannel, + 'text' => "[".date("Y-m-d H:i:s")."][" . getenv('MY_SERVER_NAME') . "][구글][광고에셋] 수신 오류 : " . $ex->getMessage(), + ]; + $this->slack->sendMessage($msgs); + } + } + + public function getAll($date = null, $accounts = null) + { + // $accounts = [["is_manager" => 0, "customerId" => "7816297493", "manageCustomer" => "7792262348"]]; + if(is_null($accounts)) { + $this->getAccounts(); + $accounts = $this->db->getAccounts(0, "AND status = 'ENABLED'"); + $lists = $accounts->getResultArray(); + } else { + $lists = $accounts; + } + $total = count($lists); + $step = 1; + CLI::write("[".date("Y-m-d H:i:s")."]"."계정/계정예산/에셋/캠페인/그룹/소재/보고서 업데이트를 시작합니다.", "light_red"); + $result = []; + foreach ($lists as $account) { + CLI::showProgress($step++, $total); + $this->getAccountBudgets($account['manageCustomer'], $account['customerId']); + $assets = $this->getAsset($account['manageCustomer'], $account['customerId']); + + $campaigns = $this->getCampaigns($account['manageCustomer'], $account['customerId']); + $result['campaign'][] = $campaigns; + if (count($campaigns)) { + $adGroups = $this->getAdGroups($account['manageCustomer'], $account['customerId']); + $ads = $this->getAds($account['manageCustomer'], $account['customerId'], null, $date); + $result['adGroups'][] = $adGroups; + $result['ads'][] = $ads; + } + } + $msgs = [ + 'channel' => $this->slackChannel, + 'text' => "[".date("Y-m-d H:i:s")."][" . getenv('MY_SERVER_NAME') . "][구글][광고계정/계정예산/에셋/캠페인/그룹/소재 정보] 수신 완료", + ]; + $this->slack->sendMessage($msgs); + return $result; + } + + public function getAdSchedules($accounts = null) { + if(is_null($accounts)) { + $accounts = $this->db->getAccounts(0, "AND status = 'ENABLED'"); + $lists = $accounts->getResultArray(); + } else { + $lists = $accounts; + } + $total = count($lists); + $step = 1; + CLI::write("[".date("Y-m-d H:i:s")."]"."광고일정 업데이트를 시작합니다.", "light_red"); + $result = []; + foreach ($lists as $account) { + CLI::showProgress($step++, $total); + $criterions = $this->getCriterions($account['manageCustomer'], $account['customerId']); + $result[] = $criterions; + } + return $result; + } + + public function setManualUpdate($campaigns) + { + if(!$campaigns){return false;} + foreach ($campaigns as $campaign) { + $campaignResult = $this->getCampaigns($campaign['manageCustomer'], $campaign['customerId'], $campaign['id']); + if(!$campaignResult){ + return false; + } + + $adGroupResult = $this->getAdGroups($campaign['manageCustomer'], $campaign['customerId'], $campaign['id']); + if(!$adGroupResult){ + return false; + } + + foreach ($adGroupResult as $adGroup) { + $adResult[] = $this->getAds($campaign['manageCustomer'], $campaign['customerId'], $adGroup['id']); + if(!$adResult){ + return false; + } + } + } + + return true; + } + + public function landingGroup($title) + { + $result = array('name' => '', 'media' => '', 'event_seq' => '', 'site' => '', 'db_price' => 0, 'period_ad' => ''); + if (empty($title)) return $result; + preg_match_all('/^.*?\#([0-9]+)?(\_([0-9]+))?([\s]+)?(\*([0-9]+)?)?([\s]+)?(\&([a-z]+))?([\s]+)?(\^([0-9]+))?/i', $title, $matches); + if (empty($matches[9][0])) { // site underscore exception + preg_match_all('/\#([0-9]+)?(\_([0-9]+))?(\_([0-9]+))?([\s]+)?(\*([0-9]+)?)?([\s]+)?(\&([a-z]+))?([\s]+)?(\^([0-9]+))?/i', $title, $matches_re); + $matches[9][0] = $matches_re[11][0] ?? ''; + $matches[3][0] = ($matches[3][0] ?? '') . ($matches_re[4][0] ?? ''); + $matches[6][0] = $matches_re[8][0] ?? 0; + $matches[12][0] = $matches_re[14][0] ?? ''; + // $matches[12][0] = $matches_re[14][0]; + } + // echo '
' . print_r($matches_re, 1) . '
'; + if (empty($matches[1][0])) { // Event SEQ를 추출할 수 없다면, $title 변수에 캠페인명이 넘어왔다고 보고 다른 로직으로 $matches 대입 + preg_match_all('/^([^>]+)?>([^|]+)(>[^|]+)||((http|https):\/\/[^\"\'\s()]+)$/', $title, $mc); + $code = explode('>', $mc[2][0] ?? ''); + $matches[1][0] = trim($code[0] ?? ''); + $matches[6][0] = trim($code[1] ?? 0); + $matches[9][0] = trim($code[2] ?? ''); + $matches[12][0] = trim(str_replace('^', '', $code[3] ?? '')); + $url = @$mc[4][4]; + $qs = parse_url($url, PHP_URL_QUERY); + parse_str($qs, $params); + $matches[3][0] = trim($params['site'] ?? ''); + } + switch ($matches[9][0]) { + case 'ger': $media = '이벤트 랜딩'; break; + case 'gercpm': $media = '이벤트 랜딩_cpm'; break; + case 'cpm': $media = 'cpm'; break; + default: $media = ''; break; + } + + if ($media) { + $period_ad = isset($matches[12][0]) && $matches[12][0] ? $matches[12][0] : 0; + $result['name'] = $title; + $result['media'] = $media; + $result['event_seq'] = $matches[1][0]; + $result['site'] = $matches[3][0]; + $result['db_price'] = $matches[6][0]; + $result['period_ad'] = $period_ad; + return $result; + } + return $result; + } + + public function getAdsUseLanding($date = null, $ids = []) + { //유효DB 개수 업데이트 + if ($date == null) { + $date = date('Y-m-d'); + } + $step = 1; + $ads = $this->db->getAdLeads($date, $ids); + $total = $ads->getNumRows(); + if (empty($total)) { + return null; + } + $i = 0; + $result = []; + CLI::write("[".date("Y-m-d H:i:s")."]"."유효DB 개수 수신을 시작합니다.", "light_red"); + foreach ($ads->getResultArray() as $row) { + $error = []; + CLI::showProgress($step++, $total); + + $title = (trim($row['code']) ? $row['code'] : (strpos($row['ad_name'], '#') !== false ? $row['ad_name'] : $row['campaign_name'] . '||' . $row['finalUrl'])); + + // CLI::write("[".date('[H:i:s]') ."] 광고({$row['ad_id']}) 유효DB개수 업데이트 - {$title}"); + + $landing = []; + $landing = $this->landingGroup($title); + if($landing['db_price'] == '') { + $landing['db_price'] = 0; + } + $data = []; + $data = [ + 'date' => $date, + 'ad_id' => $row['ad_id'] + ]; + $adGroupId = $row['adgroup_id']; + $data = array_merge($data, $landing); + + if (!empty($landing) && !preg_match('/cpm/', $landing['media'])) { + if (!$landing['event_seq']){ + $error[] = $row['ad_name'] . '(' . $row['ad_id'] . '): 이벤트번호 미입력' . PHP_EOL; + } + if (!$landing['db_price']){ + $error[] = $row['ad_name'] . '(' . $row['ad_id'] . '): DB단가 미입력' . PHP_EOL; + } + } + if(empty($landing) && isset($row['ad_name']) && preg_match('/&[a-z]+/', $row['ad_name'])){ + $error[] = $row['ad_name'] . '(' . $row['ad_id'] . '): 인식 오류' . PHP_EOL; + } + if(!empty($error)){ + foreach($error as $err){ + CLI::write("{$err}", "light_purple"); + } + } + if(empty($landing)){ + continue; + } + $dp = $this->db->getDbPrice($data); + $leads = $this->db->getLeads($data); + + $cpm = false; + if(is_null($leads) && isset($data['media']) && preg_match('/cpm/i', $data['media'])){ + $cpm = true; + } + $db_price = $data['db_price'] ?? 0; + if(isset($dp['db_price']) && $data['date'] != date('Y-m-d')){ + $db_price = $data['db_price'] = $dp['db_price']; + } + /* + *수익, 매출액 계산 + !xxxcpm - 유효db n / 수익,매출0 + !cpm - 유효db 0 / 수익,매출0 + !period - ^25 = *0.25 + */ + $sp_data = json_decode($row['spend_data'],1); + $period_margin = []; + if(!isset($data['event_seq']) && isset($data['media'])) { + $h = 0; + foreach($sp_data as $hour => $spend) { + $spend = $h == 0 ? $sp_data[$h] : $sp_data[$h-1] - $sp_data[$h]; + $margin = 0; + if($data['period_ad']) { + $period_ad = is_numeric($data['period_ad']) ? $data['period_ad'] : 0; + $margin = $spend * ('0.' . $period_ad); + } + $data['data'][] = ['hour' => $hour,'spend' => $spend,'count' => "",'sales' => "",'margin' => $margin]; + $h++; + } + } + $initZero = false; + //cpm (fhrm, fhspcpm, jhrcpm) 계산을 무효화 + if(isset($data['media'])){ + if(preg_match('/cpm/i', $data['media'])){ + $initZero = true; + } + } + $lead = []; + if(!is_null($leads)) { + foreach($leads->getResultArray() as $row) { + $sales = 0; + $db_count = $row['db_count']; + if($db_price) $sales = $db_price * $db_count; + if($initZero) $sales = 0; + //if(preg_match('/cpm/i', $data['media'])) $db_count = 0; + $lead[$row['hour']] = [ + 'sales' => $sales + ,'db_count' => $db_count + ]; + } + } + $_spend = $_count = $_sales = $_margin = 0; + for($i=0; $i<=23; $i++) { //DB수량이 없어도 지출금액이 갱신되어야하기 때문에 0~23시까지 모두 저장 + $hour = $i; + if(!isset($sp_data[$i])) $sp_data[$i] = 0; + $spend = $i == 0 ? $sp_data[$i] : $sp_data[$i] - $sp_data[$i-1]; + $count = $lead[$i]['db_count'] ?? 0; + $sales = $lead[$i]['sales'] ?? 0; + $margin = $sales - $spend; + if($sales == 0 && $count == 0 && $sp_data[$i] == 0) $margin = 0; + if($initZero) $margin = $sales = 0; + if($data['period_ad'] && $sp_data[$i] != 0) { + $period_ad = is_numeric($data['period_ad']) ? $data['period_ad'] : 0; + $margin = $spend * ('0.' . $period_ad); + } + + $_spend += $spend; + $_count += $count; + $_sales += $sales; + $_margin += $margin; + + $data['data'][] = [ + 'hour' => $hour + ,'count' => $count + ,'sales' => $sales + ,'margin' => $margin + ]; + $result = array_merge($result, $data); + } + $data['total'][] = [ + 'count' => $_count + ,'sales' => $_sales + ,'margin' => $_margin + ]; + if(isset($data['ad_id'])) { + $this->db->updateReportByHour($data); + $this->db->updateReport($data); + } + // 광고그룹별 시간대별 합산 데이터 저장 + if (!isset($adGroupData[$adGroupId])) { + $adGroupData[$adGroupId] = [ + 'count' => array_fill(0, 24, 0), + 'sales' => array_fill(0, 24, 0), + 'margin' => array_fill(0, 24, 0) + ]; + } + + for ($i = 0; $i <= 23; $i++) { + if(is_null($sp_data[$i])) $sp_data[$i] = 0; + $spend = $i == 0 ? $sp_data[$i] : $sp_data[$i] - $sp_data[$i-1]; + $adGroupData[$adGroupId]['count'][$i] += $lead[$i]['db_count'] ?? 0; + $adGroupData[$adGroupId]['sales'][$i] += $lead[$i]['sales'] ?? 0; + $adGroupData[$adGroupId]['margin'][$i] += ($lead[$i]['sales'] ?? 0) - ($spend); + if($adGroupData[$adGroupId]['sales'][$i] == 0 && $adGroupData[$adGroupId]['count'][$i] == 0 && $sp_data[$i] == 0) $adGroupData[$adGroupId]['margin'][$i] = 0; + } + } + // 광고그룹별 데이터 DB에 저장 + foreach ($adGroupData as $adGroupId => $data) { + $this->db->updateAdGroupreport($adGroupId, $date, $data); + } + CLI::write("[".date("Y-m-d H:i:s")."]"."유효DB 개수 수신을 완료합니다.", "light_red"); + return $result; + } + + public static function exception_handler($e) + { + //echo nl2br(print_r($e,1)); + echo (''); + print_r($e); + echo (''); + return true; + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..6fab5a0 --- /dev/null +++ b/composer.json @@ -0,0 +1,8 @@ +{ + "require": { + "googleads/google-ads-php": "*", + "symfony/console": "*", + "monolog/monolog": "^2.0", + "react/http": "1.9" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..f664826 --- /dev/null +++ b/composer.lock @@ -0,0 +1,2870 @@ +{ + "_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": "f98d36217a1ee885091abe411d536ae8", + "packages": [ + { + "name": "brick/math", + "version": "0.12.1", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "5.16.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2023-11-29T23:19:16+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fig/http-message-util", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message-util.git", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message-util/zipball/9d94dc0154230ac39e5bf89398b324a86f63f765", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "suggest": { + "psr/http-message": "The package containing the PSR-7 interfaces" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Fig\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Utility classes and constants for use with PSR-7 (psr/http-message)", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-message-util/issues", + "source": "https://github.com/php-fig/http-message-util/tree/1.1.5" + }, + "time": "2020-11-24T22:02:12+00:00" + }, + { + "name": "firebase/php-jwt", + "version": "v6.10.1", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "500501c2ce893c824c801da135d02661199f60c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/500501c2ce893c824c801da135d02661199f60c5", + "reference": "500501c2ce893c824c801da135d02661199f60c5", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.10.1" + }, + "time": "2024-05-18T18:05:11+00:00" + }, + { + "name": "google/auth", + "version": "v1.40.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/google-auth-library-php.git", + "reference": "bff9f2d01677e71a98394b5ac981b99523df5178" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/bff9f2d01677e71a98394b5ac981b99523df5178", + "reference": "bff9f2d01677e71a98394b5ac981b99523df5178", + "shasum": "" + }, + "require": { + "firebase/php-jwt": "^6.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.4.5", + "php": "^8.0", + "psr/cache": "^2.0||^3.0", + "psr/http-message": "^1.1||^2.0" + }, + "require-dev": { + "guzzlehttp/promises": "^2.0", + "kelvinmo/simplejwt": "0.7.1", + "phpseclib/phpseclib": "^3.0.35", + "phpspec/prophecy-phpunit": "^2.1", + "phpunit/phpunit": "^9.6", + "sebastian/comparator": ">=1.2.3", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^6.0||^7.0", + "webmozart/assert": "^1.11" + }, + "suggest": { + "phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings or for token management. Please require version ^2." + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Auth\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Google Auth Library for PHP", + "homepage": "http://github.com/google/google-auth-library-php", + "keywords": [ + "Authentication", + "google", + "oauth2" + ], + "support": { + "docs": "https://googleapis.github.io/google-auth-library-php/main/", + "issues": "https://github.com/googleapis/google-auth-library-php/issues", + "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.40.0" + }, + "time": "2024-05-31T19:16:15+00:00" + }, + { + "name": "google/common-protos", + "version": "v4.6.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/common-protos-php.git", + "reference": "f8588298a0a204aef2db15ce501530e476ec883f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/common-protos-php/zipball/f8588298a0a204aef2db15ce501530e476ec883f", + "reference": "f8588298a0a204aef2db15ce501530e476ec883f", + "shasum": "" + }, + "require": { + "google/protobuf": "^v3.25.3||^4.26.1", + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Api\\": "src/Api", + "Google\\Iam\\": "src/Iam", + "Google\\Rpc\\": "src/Rpc", + "Google\\Type\\": "src/Type", + "Google\\Cloud\\": "src/Cloud", + "GPBMetadata\\Google\\Api\\": "metadata/Api", + "GPBMetadata\\Google\\Iam\\": "metadata/Iam", + "GPBMetadata\\Google\\Rpc\\": "metadata/Rpc", + "GPBMetadata\\Google\\Type\\": "metadata/Type", + "GPBMetadata\\Google\\Cloud\\": "metadata/Cloud", + "GPBMetadata\\Google\\Logging\\": "metadata/Logging" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Google API Common Protos for PHP", + "homepage": "https://github.com/googleapis/common-protos-php", + "keywords": [ + "google" + ], + "support": { + "issues": "https://github.com/googleapis/common-protos-php/issues", + "source": "https://github.com/googleapis/common-protos-php/tree/v4.6.0" + }, + "time": "2024-04-03T19:11:54+00:00" + }, + { + "name": "google/gax", + "version": "v1.34.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/gax-php.git", + "reference": "28aa3e95969a75b278606a88448992a6396a119e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/gax-php/zipball/28aa3e95969a75b278606a88448992a6396a119e", + "reference": "28aa3e95969a75b278606a88448992a6396a119e", + "shasum": "" + }, + "require": { + "google/auth": "^1.34.0", + "google/common-protos": "^4.4", + "google/grpc-gcp": "^0.4", + "google/longrunning": "~0.4", + "google/protobuf": "^v3.25.3||^4.26.1", + "grpc/grpc": "^1.13", + "guzzlehttp/promises": "^2.0", + "guzzlehttp/psr7": "^2.0", + "php": "^8.0", + "ramsey/uuid": "^4.0" + }, + "conflict": { + "ext-protobuf": "<3.7.0" + }, + "require-dev": { + "phpspec/prophecy-phpunit": "^2.1", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.6", + "squizlabs/php_codesniffer": "3.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\ApiCore\\": "src", + "GPBMetadata\\ApiCore\\": "metadata/ApiCore" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Google API Core for PHP", + "homepage": "https://github.com/googleapis/gax-php", + "keywords": [ + "google" + ], + "support": { + "issues": "https://github.com/googleapis/gax-php/issues", + "source": "https://github.com/googleapis/gax-php/tree/v1.34.0" + }, + "time": "2024-05-30T00:35:13+00:00" + }, + { + "name": "google/grpc-gcp", + "version": "v0.4.0", + "source": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/grpc-gcp-php.git", + "reference": "2a80dbf690922aa52bb6bb79b9a32a9637a5c2d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GoogleCloudPlatform/grpc-gcp-php/zipball/2a80dbf690922aa52bb6bb79b9a32a9637a5c2d9", + "reference": "2a80dbf690922aa52bb6bb79b9a32a9637a5c2d9", + "shasum": "" + }, + "require": { + "google/auth": "^1.3", + "google/protobuf": "^v3.25.3||^4.26.1", + "grpc/grpc": "^v1.13.0", + "php": "^8.0", + "psr/cache": "^1.0.1||^2.0.0||^3.0.0" + }, + "require-dev": { + "google/cloud-spanner": "^1.7", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Grpc\\Gcp\\": "src/" + }, + "classmap": [ + "src/generated/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "gRPC GCP library for channel management", + "support": { + "issues": "https://github.com/GoogleCloudPlatform/grpc-gcp-php/issues", + "source": "https://github.com/GoogleCloudPlatform/grpc-gcp-php/tree/v0.4.0" + }, + "time": "2024-04-03T16:37:55+00:00" + }, + { + "name": "google/longrunning", + "version": "0.4.3", + "source": { + "type": "git", + "url": "https://github.com/googleapis/php-longrunning.git", + "reference": "ed718a735e407826c3332b7197a44602eb03e608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/php-longrunning/zipball/ed718a735e407826c3332b7197a44602eb03e608", + "reference": "ed718a735e407826c3332b7197a44602eb03e608", + "shasum": "" + }, + "require-dev": { + "google/gax": "^1.34.0", + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "component": { + "id": "longrunning", + "path": "LongRunning", + "entry": null, + "target": "googleapis/php-longrunning" + } + }, + "autoload": { + "psr-4": { + "Google\\LongRunning\\": "src/LongRunning", + "Google\\ApiCore\\LongRunning\\": "src/ApiCore/LongRunning", + "GPBMetadata\\Google\\Longrunning\\": "metadata/Longrunning" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Google LongRunning Client for PHP", + "support": { + "source": "https://github.com/googleapis/php-longrunning/tree/v0.4.3" + }, + "time": "2024-06-01T03:14:01+00:00" + }, + { + "name": "google/protobuf", + "version": "v4.27.1", + "source": { + "type": "git", + "url": "https://github.com/protocolbuffers/protobuf-php.git", + "reference": "c471e2b3afe61bf41f22d1ca926b24e7ce96c598" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/c471e2b3afe61bf41f22d1ca926b24e7ce96c598", + "reference": "c471e2b3afe61bf41f22d1ca926b24e7ce96c598", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": ">=5.0.0" + }, + "suggest": { + "ext-bcmath": "Need to support JSON deserialization" + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Protobuf\\": "src/Google/Protobuf", + "GPBMetadata\\Google\\Protobuf\\": "src/GPBMetadata/Google/Protobuf" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "proto library for PHP", + "homepage": "https://developers.google.com/protocol-buffers/", + "keywords": [ + "proto" + ], + "support": { + "source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.27.1" + }, + "time": "2024-06-05T16:59:28+00:00" + }, + { + "name": "googleads/google-ads-php", + "version": "v23.0.1", + "source": { + "type": "git", + "url": "https://github.com/googleads/google-ads-php.git", + "reference": "7ff9c4685a037e59d15b1774cc972d27d07e7a8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleads/google-ads-php/zipball/7ff9c4685a037e59d15b1774cc972d27d07e7a8c", + "reference": "7ff9c4685a037e59d15b1774cc972d27d07e7a8c", + "shasum": "" + }, + "require": { + "google/gax": "^1.19.1", + "google/protobuf": "^3.21.5 || ^4.26", + "grpc/grpc": "^1.36.0", + "monolog/monolog": "^1.26 || ^2.0 || ^3.0", + "php": ">=8.1" + }, + "require-dev": { + "composer/composer": "^2.0", + "ext-bcmath": "*", + "ext-grpc": "*", + "ext-protobuf": "*", + "phpunit/phpunit": "^9.5", + "react/http": "^1.2.0", + "squizlabs/php_codesniffer": "^3.5", + "ulrichsg/getopt-php": "^3.4" + }, + "suggest": { + "ext-grpc": "To be able to use the gRPC transport, use the C implementation of gRPC", + "ext-protobuf": "For better performance, use the C implementation of Protobuf", + "google/protobuf": "In case the C implementation of Protobuf is not suitable, use the PHP one", + "grpc/grpc": "In case the C implementation of gRPC is not suitable, use the PHP one to enable other transports", + "react/http": "To run the AuthenticateInWebApplication.php example" + }, + "type": "library", + "autoload": { + "psr-4": { + "Google\\Ads\\GoogleAds\\": "src/Google/Ads/GoogleAds", + "GPBMetadata\\Google\\Ads\\GoogleAds\\": "metadata/Google/Ads/GoogleAds" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Google", + "homepage": "https://github.com/googleads/google-ads-php/contributors" + } + ], + "description": "Google Ads API client for PHP", + "homepage": "https://github.com/googleads/google-ads-php", + "support": { + "issues": "https://github.com/googleads/google-ads-php/issues", + "source": "https://github.com/googleads/google-ads-php/tree/v23.0.1" + }, + "time": "2024-04-30T16:02:36+00:00" + }, + { + "name": "grpc/grpc", + "version": "1.57.0", + "source": { + "type": "git", + "url": "https://github.com/grpc/grpc-php.git", + "reference": "b610c42022ed3a22f831439cb93802f2a4502fdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grpc/grpc-php/zipball/b610c42022ed3a22f831439cb93802f2a4502fdf", + "reference": "b610c42022ed3a22f831439cb93802f2a4502fdf", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "google/auth": "^v1.3.0" + }, + "suggest": { + "ext-protobuf": "For better performance, install the protobuf C extension.", + "google/protobuf": "To get started using grpc quickly, install the native protobuf library." + }, + "type": "library", + "autoload": { + "psr-4": { + "Grpc\\": "src/lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "gRPC library for PHP", + "homepage": "https://grpc.io", + "keywords": [ + "rpc" + ], + "support": { + "source": "https://github.com/grpc/grpc-php/tree/v1.57.0" + }, + "time": "2023-08-14T23:57:54+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:35:24+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:19:20+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.6.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.6.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:05:35+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.9.3", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "a30bfe2e142720dfa990d0a7e573997f5d884215" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/a30bfe2e142720dfa990d0a7e573997f5d884215", + "reference": "a30bfe2e142720dfa990d0a7e573997f5d884215", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpspec/prophecy": "^1.15", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.38 || ^9.6.19", + "predis/predis": "^1.1 || ^2.0", + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.9.3" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2024-04-12T20:52:51+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/1.1" + }, + "time": "2023-04-04T09:50:52+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.28.3", + "fakerphp/faker": "^1.21", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^1.0", + "mockery/mockery": "^1.5", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcsstandards/phpcsutils": "^1.0.0-rc1", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18.4", + "ramsey/coding-standard": "^2.0.3", + "ramsey/conventional-commits": "^1.3", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2022-12-31T21:50:55+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.7.6", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", + "ext-json": "*", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^8.5 || ^9", + "ramsey/composer-repl": "^1.4", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.9" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.7.6" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2024-04-27T21:32:50+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/dns", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "c134600642fa615b46b41237ef243daa65bb64ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/c134600642fa615b46b41237ef243daa65bb64ec", + "reference": "c134600642fa615b46b41237ef243daa65bb64ec", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.0 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4 || ^3 || ^2", + "react/promise-timer": "^1.9" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.12.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-29T12:41:06+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" + }, + { + "name": "react/http", + "version": "v1.9.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/http.git", + "reference": "bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/http/zipball/bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0", + "reference": "bb3154dbaf2dfe3f0467f956a05f614a69d5f1d0", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "fig/http-message-util": "^1.1", + "php": ">=5.3.0", + "psr/http-message": "^1.0", + "react/event-loop": "^1.2", + "react/promise": "^3 || ^2.3 || ^1.2.1", + "react/socket": "^1.12", + "react/stream": "^1.2", + "ringcentral/psr7": "^1.2" + }, + "require-dev": { + "clue/http-proxy-react": "^1.8", + "clue/reactphp-ssh-proxy": "^1.4", + "clue/socks-react": "^1.4", + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/async": "^4 || ^3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.9" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Http\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven, streaming HTTP client and server implementation for ReactPHP", + "keywords": [ + "async", + "client", + "event-driven", + "http", + "http client", + "http server", + "https", + "psr-7", + "reactphp", + "server", + "streaming" + ], + "support": { + "issues": "https://github.com/reactphp/http/issues", + "source": "https://github.com/reactphp/http/tree/v1.9.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-04-26T10:29:24+00:00" + }, + { + "name": "react/promise", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-05-24T10:39:05+00:00" + }, + { + "name": "react/socket", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.11", + "react/event-loop": "^1.2", + "react/promise": "^3 || ^2.6 || ^1.2.1", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4 || ^3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.15.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-12-15T11:02:10+00:00" + }, + { + "name": "react/stream", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/6fbc9672905c7d5a885f2da2fc696f65840f4a66", + "reference": "6fbc9672905c7d5a885f2da2fc696f65840f4a66", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-06-16T10:52:11+00:00" + }, + { + "name": "ringcentral/psr7", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/ringcentral/psr7.git", + "reference": "360faaec4b563958b673fb52bbe94e37f14bc686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ringcentral/psr7/zipball/360faaec4b563958b673fb52bbe94e37f14bc686", + "reference": "360faaec4b563958b673fb52bbe94e37f14bc686", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "RingCentral\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "PSR-7 message implementation", + "keywords": [ + "http", + "message", + "stream", + "uri" + ], + "support": { + "source": "https://github.com/ringcentral/psr7/tree/master" + }, + "time": "2018-05-29T20:21:04+00:00" + }, + { + "name": "symfony/console", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "9b008f2d7b21c74ef4d0c3de6077a642bc55ece3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/9b008f2d7b21c74ef4d0c3de6077a642bc55ece3", + "reference": "9b008f2d7b21c74ef4d0c3de6077a642bc55ece3", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/string", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "60bc311c74e0af215101235aa6f471bcbc032df2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/60bc311c74e0af215101235aa6f471bcbc032df2", + "reference": "60bc311c74e0af215101235aa6f471bcbc032df2", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-04T06:40:14+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +}