feat: Dodaj moduł grup i fraz, oznaczanie wykluczonych na czerwono, CLAUDE.md
- Nowy moduł CampaignTerms z widokiem grup reklam, fraz wyszukiwanych i fraz wykluczających - Frazy wyszukiwane dodane do wykluczonych oznaczane czerwonym kolorem w tabeli - Instalator migracji (install.php) z obsługą schema_migrations - Migracja 003 dla tabel campaign_ad_groups, campaign_search_terms, campaign_negative_keywords - CLAUDE.md z dokumentacją architektury projektu - Aktualizacja layoutu, stylów i konfiguracji Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,9 @@
|
|||||||
"WebFetch(domain:www.storegrowers.com)",
|
"WebFetch(domain:www.storegrowers.com)",
|
||||||
"WebFetch(domain:platform.openai.com)",
|
"WebFetch(domain:platform.openai.com)",
|
||||||
"WebFetch(domain:openai.com)",
|
"WebFetch(domain:openai.com)",
|
||||||
"Bash(sass:*)"
|
"Bash(sass:*)",
|
||||||
|
"WebFetch(domain:developers.google.com)",
|
||||||
|
"Bash(cd:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
99
CLAUDE.md
Normal file
99
CLAUDE.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
adsPRO is a PHP SaaS application for managing Google Ads campaigns, products, and clients. It integrates with Google Ads API, OpenAI, and Claude AI to provide AI-powered ad optimization. UI language is Polish.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Custom lightweight MVC framework with three layers:
|
||||||
|
|
||||||
|
- **Controllers** (`autoload/controls/class.*.php`, namespace `\controls`) - handle requests, return rendered views or JSON
|
||||||
|
- **Factories** (`autoload/factory/class.*.php`, namespace `\factory`) - data access layer, all static methods, use Medoo ORM
|
||||||
|
- **Views** (`autoload/view/class.*.php`, namespace `\view`) - compose templates with data
|
||||||
|
- **Services** (`autoload/services/class.*.php`, namespace `\services`) - external API integrations (GoogleAdsApi, ClaudeApi, OpenAiApi)
|
||||||
|
- **Templates** (`templates/`) - PHP files, variables accessed via `$this->varName`
|
||||||
|
|
||||||
|
### Autoloading
|
||||||
|
|
||||||
|
PSR-0-like: `\controls\Campaigns` resolves to `autoload/controls/class.Campaigns.php`.
|
||||||
|
|
||||||
|
### Routing
|
||||||
|
|
||||||
|
Entry point: `index.php`. URL `/module/action/key=value` maps to `\controls\Module::action()`. Route aliases defined in `$route_aliases` array. Default route: `campaigns/main_view`.
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
MySQL via Medoo ORM (`global $mdb`). Common patterns:
|
||||||
|
```php
|
||||||
|
$mdb->select('table', '*', ['field' => $value]);
|
||||||
|
$mdb->get('table', '*', ['id' => $id]);
|
||||||
|
$mdb->insert('table', ['field' => $value]);
|
||||||
|
$mdb->update('table', ['field' => $value], ['id' => $id]);
|
||||||
|
$mdb->query($sql, [':param' => $value])->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Utility Classes
|
||||||
|
|
||||||
|
- `\S` - static helpers: `\S::get('param')` (POST/GET), `\S::get_session()`, `\S::set_session()`, `\S::alert()`, `\S::send_email()`
|
||||||
|
- `\Tpl::view('path/template', ['var' => $data])` - template rendering
|
||||||
|
- `\Html::input()`, `\Html::select()`, etc. - form component builders
|
||||||
|
- `\Cache::store()`, `\Cache::fetch()` - file-based caching
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
Session-based with cookie auto-login. User stored in `$_SESSION['user']`. Public paths whitelisted in `index.php`. IP validated per session.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### Database Migrations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run migrations (via browser or CLI)
|
||||||
|
php install.php
|
||||||
|
|
||||||
|
# With demo data
|
||||||
|
php install.php --with_demo
|
||||||
|
|
||||||
|
# Force re-run all
|
||||||
|
php install.php --force
|
||||||
|
```
|
||||||
|
|
||||||
|
Migration files in `migrations/` follow pattern `NNN_description.sql`. Tracked in `schema_migrations` table (idempotent).
|
||||||
|
|
||||||
|
### SASS Compilation
|
||||||
|
|
||||||
|
VS Code Live Sass Compiler watches `layout/style.scss` and compiles to `layout/style.css` (compressed).
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
Files auto-upload to remote server via VS Code FTP-Kr extension (`.vscode/ftp-kr.json`). No build step required.
|
||||||
|
|
||||||
|
## Code Conventions
|
||||||
|
|
||||||
|
- **PHP style**: Spaces inside parentheses `if ( $x )`, braces on new line, 2-space indent in templates, 4-space in classes
|
||||||
|
- **Naming**: Classes PascalCase, methods/variables/columns snake_case, namespaces lowercase
|
||||||
|
- **Static methods**: Controllers and factories use `static public function`
|
||||||
|
- **JSON endpoints**: `echo json_encode([...]); exit;`
|
||||||
|
- **Template variables**: passed as array to `\Tpl::view()`, accessed as `$this->varName`
|
||||||
|
|
||||||
|
## Frontend Stack
|
||||||
|
|
||||||
|
jQuery 3.6, DataTables 2.1, Bootstrap 4, Select2 4.1, Highcharts, Font Awesome 6.5, jquery-confirm for modals. All loaded via CDN or from `libraries/`.
|
||||||
|
|
||||||
|
## Entry Points
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `index.php` | Main app (routing + auth) |
|
||||||
|
| `ajax.php` | AJAX requests (authenticated) |
|
||||||
|
| `api.php` | Public API |
|
||||||
|
| `cron.php` | Background jobs |
|
||||||
|
| `install.php` | Database migration runner |
|
||||||
|
| `config.php` | DB and email credentials |
|
||||||
|
|
||||||
|
## API Settings Storage
|
||||||
|
|
||||||
|
Google Ads, Claude, and OpenAI API keys are stored in the `settings` table (key-value) and managed via the Settings page (`\controls\Users::settings`).
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<?
|
<?php
|
||||||
namespace controls;
|
namespace controls;
|
||||||
class Api
|
class Api
|
||||||
{
|
{
|
||||||
@@ -270,4 +270,257 @@ class Api
|
|||||||
echo json_encode( [ 'status' => 'ok' ] );
|
echo json_encode( [ 'status' => 'ok' ] );
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
static public function products_data_import()
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
$json = file_get_contents( 'php://input' );
|
||||||
|
$data = json_decode( $json, true );
|
||||||
|
|
||||||
|
if ( !is_array( $data ) || empty( $data['client_id'] ) || empty( $data['date'] ) || !isset( $data['data'] ) || !is_array( $data['data'] ) )
|
||||||
|
{
|
||||||
|
echo json_encode( [ 'status' => 'error', 'message' => 'Nieprawidlowe dane wejsciowe. Oczekiwano: client_id, date, data[].' ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$client_id = (int) $data['client_id'];
|
||||||
|
$date = date( 'Y-m-d', strtotime( $data['date'] ) );
|
||||||
|
|
||||||
|
if ( !$mdb -> count( 'clients', [ 'id' => $client_id ] ) )
|
||||||
|
{
|
||||||
|
echo json_encode( [ 'status' => 'error', 'message' => 'Nie znaleziono klienta o podanym ID.' ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$processed = 0;
|
||||||
|
$skipped = 0;
|
||||||
|
$touched_product_ids = [];
|
||||||
|
|
||||||
|
foreach ( $data['data'] as $offer )
|
||||||
|
{
|
||||||
|
$offer_external_id = trim( (string) ( $offer['OfferId'] ?? '' ) );
|
||||||
|
if ( $offer_external_id === '' )
|
||||||
|
{
|
||||||
|
$skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$product_title = trim( (string) ( $offer['ProductTitle'] ?? '' ) );
|
||||||
|
if ( $product_title === '' )
|
||||||
|
{
|
||||||
|
$product_title = $offer_external_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !$mdb -> count( 'products', [ 'AND' => [ 'client_id' => $client_id, 'offer_id' => $offer_external_id ] ] ) )
|
||||||
|
{
|
||||||
|
$mdb -> insert( 'products', [
|
||||||
|
'client_id' => $client_id,
|
||||||
|
'offer_id' => $offer_external_id,
|
||||||
|
'name' => $product_title
|
||||||
|
] );
|
||||||
|
$product_id = $mdb -> id();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$product_id = $mdb -> get( 'products', 'id', [ 'AND' => [ 'client_id' => $client_id, 'offer_id' => $offer_external_id ] ] );
|
||||||
|
$offer_current_name = $mdb -> get( 'products', 'name', [ 'AND' => [ 'client_id' => $client_id, 'offer_id' => $offer_external_id ] ] );
|
||||||
|
|
||||||
|
if ( $offer_current_name != $product_title and $date == date( 'Y-m-d', strtotime( '-1 days', time() ) ) )
|
||||||
|
{
|
||||||
|
$mdb -> update( 'products', [ 'name' => $product_title ], [ 'AND' => [ 'client_id' => $client_id, 'offer_id' => $offer_external_id ] ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !$product_id )
|
||||||
|
{
|
||||||
|
$skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$impressions = (int) round( self::normalize_number( $offer['Impressions'] ?? 0 ) );
|
||||||
|
$clicks = (int) round( self::normalize_number( $offer['Clicks'] ?? 0 ) );
|
||||||
|
$cost = self::normalize_number( $offer['Cost'] ?? 0 );
|
||||||
|
$conversions = self::normalize_number( $offer['Conversions'] ?? 0 );
|
||||||
|
$conversion_value = self::normalize_number( $offer['ConversionValue'] ?? 0 );
|
||||||
|
$ctr = ( $impressions > 0 ) ? round( $clicks / $impressions, 4 ) * 100 : 0;
|
||||||
|
|
||||||
|
$offer_data = [
|
||||||
|
'impressions' => $impressions,
|
||||||
|
'clicks' => $clicks,
|
||||||
|
'ctr' => $ctr,
|
||||||
|
'cost' => $cost,
|
||||||
|
'conversions' => $conversions,
|
||||||
|
'conversions_value' => $conversion_value,
|
||||||
|
'updated' => 1
|
||||||
|
];
|
||||||
|
|
||||||
|
if ( $mdb -> count( 'products_history', [ 'AND' => [ 'product_id' => $product_id, 'date_add' => $date ] ] ) )
|
||||||
|
{
|
||||||
|
$offer_data_old = $mdb -> get( 'products_history', '*', [ 'AND' => [ 'product_id' => $product_id, 'date_add' => $date ] ] );
|
||||||
|
|
||||||
|
if (
|
||||||
|
$offer_data_old['impressions'] == $offer_data['impressions']
|
||||||
|
and $offer_data_old['clicks'] == $offer_data['clicks']
|
||||||
|
and number_format( (float) str_replace( ',', '.', $offer_data_old['cost'] ), 5 ) == number_format( (float) $offer_data['cost'], 5 )
|
||||||
|
and (float) $offer_data_old['conversions'] == (float) $offer_data['conversions']
|
||||||
|
and number_format( (float) str_replace( ',', '.', $offer_data_old['conversions_value'] ), 5 ) == number_format( (float) $offer_data['conversions_value'], 5 )
|
||||||
|
)
|
||||||
|
{
|
||||||
|
$touched_product_ids[ $product_id ] = true;
|
||||||
|
$processed++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mdb -> update( 'products_history', $offer_data, [
|
||||||
|
'AND' => [ 'product_id' => $product_id, 'date_add' => $date ]
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$offer_data['product_id'] = $product_id;
|
||||||
|
$offer_data['date_add'] = $date;
|
||||||
|
$mdb -> insert( 'products_history', $offer_data );
|
||||||
|
}
|
||||||
|
|
||||||
|
$touched_product_ids[ $product_id ] = true;
|
||||||
|
$processed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$history_30_rows = 0;
|
||||||
|
foreach ( array_keys( $touched_product_ids ) as $product_id )
|
||||||
|
{
|
||||||
|
\controls\Cron::cron_product_history_30_save( (int) $product_id, $date );
|
||||||
|
$mdb -> update( 'products_history', [ 'updated' => 0 ], [ 'AND' => [ 'product_id' => (int) $product_id, 'date_add' => $date ] ] );
|
||||||
|
$history_30_rows++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$temp_rows = self::rebuild_products_temp_for_client( $client_id );
|
||||||
|
|
||||||
|
echo json_encode( [
|
||||||
|
'status' => 'ok',
|
||||||
|
'client_id' => $client_id,
|
||||||
|
'date' => $date,
|
||||||
|
'processed' => $processed,
|
||||||
|
'skipped' => $skipped,
|
||||||
|
'history_30_products' => $history_30_rows,
|
||||||
|
'products_temp_rows' => $temp_rows
|
||||||
|
] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private function rebuild_products_temp_for_client( $client_id )
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
$client_id = (int) $client_id;
|
||||||
|
if ( $client_id <= 0 )
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$product_ids = $mdb -> select( 'products', 'id', [ 'client_id' => $client_id ] );
|
||||||
|
if ( empty( $product_ids ) )
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mdb -> delete( 'products_temp', [ 'product_id' => $product_ids ] );
|
||||||
|
|
||||||
|
$rows = $mdb -> query(
|
||||||
|
'SELECT p.id AS product_id, p.name,
|
||||||
|
COALESCE( SUM( ph.impressions ), 0 ) AS impressions,
|
||||||
|
COALESCE( SUM( ph.clicks ), 0 ) AS clicks,
|
||||||
|
COALESCE( SUM( ph.cost ), 0 ) AS cost,
|
||||||
|
COALESCE( SUM( ph.conversions ), 0 ) AS conversions,
|
||||||
|
COALESCE( SUM( ph.conversions_value ), 0 ) AS conversions_value
|
||||||
|
FROM products AS p
|
||||||
|
LEFT JOIN products_history AS ph ON p.id = ph.product_id
|
||||||
|
WHERE p.client_id = :client_id
|
||||||
|
GROUP BY p.id, p.name',
|
||||||
|
[ ':client_id' => $client_id ]
|
||||||
|
) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||||
|
|
||||||
|
$inserted = 0;
|
||||||
|
foreach ( $rows as $row )
|
||||||
|
{
|
||||||
|
$impressions = (int) $row['impressions'];
|
||||||
|
$clicks = (int) $row['clicks'];
|
||||||
|
$cost = (float) $row['cost'];
|
||||||
|
$conversions = (float) $row['conversions'];
|
||||||
|
$conversion_value = (float) $row['conversions_value'];
|
||||||
|
$ctr = ( $impressions > 0 ) ? round( $clicks / $impressions, 4 ) * 100 : 0;
|
||||||
|
$cpc = ( $clicks > 0 ) ? round( $cost / $clicks, 6 ) : 0;
|
||||||
|
$roas = ( $cost > 0 ) ? round( $conversion_value / $cost, 2 ) * 100 : 0;
|
||||||
|
|
||||||
|
$mdb -> insert( 'products_temp', [
|
||||||
|
'product_id' => (int) $row['product_id'],
|
||||||
|
'name' => $row['name'],
|
||||||
|
'impressions' => $impressions,
|
||||||
|
'impressions_30' => (int) \factory\Products::get_impressions_30( (int) $row['product_id'] ),
|
||||||
|
'clicks' => $clicks,
|
||||||
|
'clicks_30' => (int) \factory\Products::get_clicks_30( (int) $row['product_id'] ),
|
||||||
|
'ctr' => $ctr,
|
||||||
|
'cost' => $cost,
|
||||||
|
'conversions' => $conversions,
|
||||||
|
'conversions_value' => $conversion_value,
|
||||||
|
'cpc' => $cpc,
|
||||||
|
'roas' => $roas
|
||||||
|
] );
|
||||||
|
|
||||||
|
$inserted++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $inserted;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private function normalize_number( $value )
|
||||||
|
{
|
||||||
|
if ( $value === null || $value === '' )
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( is_int( $value ) || is_float( $value ) )
|
||||||
|
{
|
||||||
|
return (float) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = trim( (string) $value );
|
||||||
|
if ( $value === '' )
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = preg_replace( '/[^\d,.\-]/', '', $value );
|
||||||
|
if ( $value === '' || $value === '-' || $value === ',' || $value === '.' )
|
||||||
|
{
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$has_comma = strpos( $value, ',' ) !== false;
|
||||||
|
$has_dot = strpos( $value, '.' ) !== false;
|
||||||
|
|
||||||
|
if ( $has_comma && $has_dot )
|
||||||
|
{
|
||||||
|
$last_comma = strrpos( $value, ',' );
|
||||||
|
$last_dot = strrpos( $value, '.' );
|
||||||
|
|
||||||
|
if ( $last_comma > $last_dot )
|
||||||
|
{
|
||||||
|
$value = str_replace( '.', '', $value );
|
||||||
|
$value = str_replace( ',', '.', $value );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$value = str_replace( ',', '', $value );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$value = str_replace( ',', '.', $value );
|
||||||
|
}
|
||||||
|
|
||||||
|
return (float) $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
177
autoload/controls/class.CampaignTerms.php
Normal file
177
autoload/controls/class.CampaignTerms.php
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
namespace controls;
|
||||||
|
|
||||||
|
class CampaignTerms
|
||||||
|
{
|
||||||
|
static public function main_view()
|
||||||
|
{
|
||||||
|
return \Tpl::view( 'campaign_terms/main_view', [
|
||||||
|
'clients' => \factory\Campaigns::get_clients(),
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function get_campaigns_list()
|
||||||
|
{
|
||||||
|
$client_id = (int) \S::get( 'client_id' );
|
||||||
|
echo json_encode( [ 'campaigns' => \factory\Campaigns::get_campaigns_list( $client_id ) ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function get_campaign_ad_groups()
|
||||||
|
{
|
||||||
|
$campaign_id = (int) \S::get( 'campaign_id' );
|
||||||
|
|
||||||
|
if ( $campaign_id <= 0 )
|
||||||
|
{
|
||||||
|
echo json_encode( [ 'ad_groups' => [] ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode( [ 'ad_groups' => \factory\Campaigns::get_campaign_ad_groups( $campaign_id ) ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function get_campaign_phrase_details()
|
||||||
|
{
|
||||||
|
$campaign_id = (int) \S::get( 'campaign_id' );
|
||||||
|
$ad_group_id = (int) \S::get( 'ad_group_id' );
|
||||||
|
|
||||||
|
if ( $campaign_id <= 0 )
|
||||||
|
{
|
||||||
|
echo json_encode( [ 'search_terms' => [], 'negative_keywords' => [] ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode( [
|
||||||
|
'search_terms' => \factory\Campaigns::get_campaign_search_terms( $campaign_id, $ad_group_id ),
|
||||||
|
'negative_keywords' => \factory\Campaigns::get_campaign_negative_keywords( $campaign_id, $ad_group_id )
|
||||||
|
] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function add_negative_keyword()
|
||||||
|
{
|
||||||
|
$search_term_id = (int) \S::get( 'search_term_id' );
|
||||||
|
$match_type = strtoupper( trim( (string) \S::get( 'match_type' ) ) );
|
||||||
|
$scope = strtolower( trim( (string) \S::get( 'scope' ) ) );
|
||||||
|
|
||||||
|
if ( $search_term_id <= 0 )
|
||||||
|
{
|
||||||
|
echo json_encode( [ 'success' => false, 'message' => 'Nie podano frazy do wykluczenia.' ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !in_array( $match_type, [ 'PHRASE', 'EXACT', 'BROAD' ], true ) )
|
||||||
|
{
|
||||||
|
$match_type = 'PHRASE';
|
||||||
|
}
|
||||||
|
if ( !in_array( $scope, [ 'campaign', 'ad_group' ], true ) )
|
||||||
|
{
|
||||||
|
$scope = 'campaign';
|
||||||
|
}
|
||||||
|
|
||||||
|
$context = \factory\Campaigns::get_search_term_context( $search_term_id );
|
||||||
|
if ( !$context )
|
||||||
|
{
|
||||||
|
echo json_encode( [ 'success' => false, 'message' => 'Nie znaleziono danych frazy.' ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$customer_id = trim( (string) ( $context['google_ads_customer_id'] ?? '' ) );
|
||||||
|
$campaign_external_id = trim( (string) ( $context['external_campaign_id'] ?? '' ) );
|
||||||
|
$ad_group_external_id = trim( (string) ( $context['external_ad_group_id'] ?? '' ) );
|
||||||
|
$keyword_text = trim( (string) ( $context['search_term'] ?? '' ) );
|
||||||
|
|
||||||
|
$missing_data = ( $customer_id === '' || $keyword_text === '' );
|
||||||
|
if ( $scope === 'campaign' && $campaign_external_id === '' )
|
||||||
|
{
|
||||||
|
$missing_data = true;
|
||||||
|
}
|
||||||
|
if ( $scope === 'ad_group' && $ad_group_external_id === '' )
|
||||||
|
{
|
||||||
|
$missing_data = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $missing_data )
|
||||||
|
{
|
||||||
|
echo json_encode( [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Brak wymaganych danych Google Ads dla tej frazy.',
|
||||||
|
'debug' => [
|
||||||
|
'customer_id' => $customer_id,
|
||||||
|
'campaign_external_id' => $campaign_external_id,
|
||||||
|
'ad_group_external_id' => $ad_group_external_id,
|
||||||
|
'keyword_text' => $keyword_text,
|
||||||
|
'scope' => $scope,
|
||||||
|
'context' => $context
|
||||||
|
]
|
||||||
|
] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$api = new \services\GoogleAdsApi();
|
||||||
|
if ( !$api -> is_configured() )
|
||||||
|
{
|
||||||
|
echo json_encode( [ 'success' => false, 'message' => 'Google Ads API nie jest skonfigurowane.' ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $scope === 'campaign' )
|
||||||
|
{
|
||||||
|
$api_result = $api -> add_negative_keyword_to_campaign( $customer_id, $campaign_external_id, $keyword_text, $match_type );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$api_result = $api -> add_negative_keyword_to_ad_group( $customer_id, $ad_group_external_id, $keyword_text, $match_type );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !( $api_result['success'] ?? false ) )
|
||||||
|
{
|
||||||
|
$last_error = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
|
||||||
|
echo json_encode( [
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Nie udalo sie zapisac frazy wykluczajacej w Google Ads.',
|
||||||
|
'error' => $last_error,
|
||||||
|
'debug' => [
|
||||||
|
'customer_id' => $customer_id,
|
||||||
|
'campaign_external_id' => $campaign_external_id,
|
||||||
|
'ad_group_external_id' => $ad_group_external_id,
|
||||||
|
'keyword_text' => $keyword_text,
|
||||||
|
'match_type' => $match_type,
|
||||||
|
'scope' => $scope,
|
||||||
|
'api_result' => $api_result
|
||||||
|
]
|
||||||
|
] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
\factory\Campaigns::upsert_campaign_negative_keyword(
|
||||||
|
(int) $context['db_campaign_id'],
|
||||||
|
$scope === 'campaign' ? null : (int) $context['db_ad_group_id'],
|
||||||
|
$scope,
|
||||||
|
$keyword_text,
|
||||||
|
$match_type
|
||||||
|
);
|
||||||
|
|
||||||
|
$scope_label = $scope === 'campaign' ? 'kampanii' : 'grupy reklam';
|
||||||
|
|
||||||
|
echo json_encode( [
|
||||||
|
'success' => true,
|
||||||
|
'message' => ( $api_result['duplicate'] ?? false ) ? 'Fraza byla juz wykluczona na poziomie ' . $scope_label . '.' : 'Fraza zostala dodana do wykluczajacych na poziomie ' . $scope_label . '.',
|
||||||
|
'duplicate' => (bool) ( $api_result['duplicate'] ?? false ),
|
||||||
|
'match_type' => $match_type,
|
||||||
|
'scope' => $scope,
|
||||||
|
'debug' => [
|
||||||
|
'customer_id' => $customer_id,
|
||||||
|
'campaign_external_id' => $campaign_external_id,
|
||||||
|
'ad_group_external_id' => $ad_group_external_id,
|
||||||
|
'keyword_text' => $keyword_text,
|
||||||
|
'scope' => $scope,
|
||||||
|
'api_response' => $api_result['response'] ?? null,
|
||||||
|
'sent_operation' => $api_result['sent_operation'] ?? null,
|
||||||
|
'verification' => $api_result['verification'] ?? null
|
||||||
|
]
|
||||||
|
] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -104,6 +104,38 @@ class Campaigns
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public function get_campaign_ad_groups()
|
||||||
|
{
|
||||||
|
$campaign_id = (int) \S::get( 'campaign_id' );
|
||||||
|
|
||||||
|
if ( $campaign_id <= 0 )
|
||||||
|
{
|
||||||
|
echo json_encode( [ 'ad_groups' => [] ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode( [ 'ad_groups' => \factory\Campaigns::get_campaign_ad_groups( $campaign_id ) ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function get_campaign_phrase_details()
|
||||||
|
{
|
||||||
|
$campaign_id = (int) \S::get( 'campaign_id' );
|
||||||
|
$ad_group_id = (int) \S::get( 'ad_group_id' );
|
||||||
|
|
||||||
|
if ( $campaign_id <= 0 )
|
||||||
|
{
|
||||||
|
echo json_encode( [ 'search_terms' => [], 'negative_keywords' => [] ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode( [
|
||||||
|
'search_terms' => \factory\Campaigns::get_campaign_search_terms( $campaign_id, $ad_group_id ),
|
||||||
|
'negative_keywords' => \factory\Campaigns::get_campaign_negative_keywords( $campaign_id, $ad_group_id )
|
||||||
|
] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
static public function delete_campaign()
|
static public function delete_campaign()
|
||||||
{
|
{
|
||||||
$campaign_id = \S::get( 'campaign_id' );
|
$campaign_id = \S::get( 'campaign_id' );
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,177 @@ class Campaigns
|
|||||||
return $mdb -> get( 'clients', 'name', [ 'id' => $client_id ] );
|
return $mdb -> get( 'clients', 'name', [ 'id' => $client_id ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public function get_campaign_ad_groups( $campaign_id )
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
return $mdb -> query(
|
||||||
|
'SELECT
|
||||||
|
id,
|
||||||
|
campaign_id,
|
||||||
|
ad_group_id,
|
||||||
|
ad_group_name,
|
||||||
|
impressions_30,
|
||||||
|
clicks_30,
|
||||||
|
cost_30,
|
||||||
|
conversions_30,
|
||||||
|
conversion_value_30,
|
||||||
|
roas_30,
|
||||||
|
impressions_all_time,
|
||||||
|
clicks_all_time,
|
||||||
|
cost_all_time,
|
||||||
|
conversions_all_time,
|
||||||
|
conversion_value_all_time,
|
||||||
|
roas_all_time
|
||||||
|
FROM campaign_ad_groups
|
||||||
|
WHERE campaign_id = :campaign_id
|
||||||
|
ORDER BY clicks_30 DESC, clicks_all_time DESC, ad_group_name ASC',
|
||||||
|
[ ':campaign_id' => (int) $campaign_id ]
|
||||||
|
) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function get_campaign_search_terms( $campaign_id, $ad_group_id = 0 )
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
$sql = 'SELECT
|
||||||
|
st.id,
|
||||||
|
st.campaign_id,
|
||||||
|
st.ad_group_id,
|
||||||
|
ag.ad_group_name,
|
||||||
|
st.search_term,
|
||||||
|
st.impressions_30,
|
||||||
|
st.clicks_30,
|
||||||
|
st.cost_30,
|
||||||
|
st.conversions_30,
|
||||||
|
st.conversion_value_30,
|
||||||
|
st.roas_30,
|
||||||
|
st.impressions_all_time,
|
||||||
|
st.clicks_all_time,
|
||||||
|
st.cost_all_time,
|
||||||
|
st.conversions_all_time,
|
||||||
|
st.conversion_value_all_time,
|
||||||
|
st.roas_all_time
|
||||||
|
FROM campaign_search_terms AS st
|
||||||
|
LEFT JOIN campaign_ad_groups AS ag ON ag.id = st.ad_group_id
|
||||||
|
WHERE st.campaign_id = :campaign_id';
|
||||||
|
|
||||||
|
$params = [ ':campaign_id' => (int) $campaign_id ];
|
||||||
|
|
||||||
|
if ( (int) $ad_group_id > 0 )
|
||||||
|
{
|
||||||
|
$sql .= ' AND st.ad_group_id = :ad_group_id';
|
||||||
|
$params[':ad_group_id'] = (int) $ad_group_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= ' ORDER BY st.clicks_30 DESC, st.clicks_all_time DESC, st.search_term ASC';
|
||||||
|
|
||||||
|
return $mdb -> query( $sql, $params ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function get_campaign_negative_keywords( $campaign_id, $ad_group_id = 0 )
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
$sql = 'SELECT
|
||||||
|
nk.id,
|
||||||
|
nk.campaign_id,
|
||||||
|
nk.ad_group_id,
|
||||||
|
ag.ad_group_name,
|
||||||
|
nk.scope,
|
||||||
|
nk.keyword_text,
|
||||||
|
nk.match_type
|
||||||
|
FROM campaign_negative_keywords AS nk
|
||||||
|
LEFT JOIN campaign_ad_groups AS ag ON ag.id = nk.ad_group_id
|
||||||
|
WHERE nk.campaign_id = :campaign_id';
|
||||||
|
|
||||||
|
$params = [ ':campaign_id' => (int) $campaign_id ];
|
||||||
|
|
||||||
|
if ( (int) $ad_group_id > 0 )
|
||||||
|
{
|
||||||
|
$sql .= ' AND ( nk.scope = \'campaign\' OR nk.ad_group_id = :ad_group_id )';
|
||||||
|
$params[':ad_group_id'] = (int) $ad_group_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= ' ORDER BY nk.scope ASC, nk.keyword_text ASC';
|
||||||
|
|
||||||
|
return $mdb -> query( $sql, $params ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function get_search_term_context( $search_term_row_id )
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
return $mdb -> query(
|
||||||
|
'SELECT
|
||||||
|
st.id AS search_term_row_id,
|
||||||
|
st.search_term,
|
||||||
|
st.campaign_id AS db_campaign_id,
|
||||||
|
st.ad_group_id AS db_ad_group_id,
|
||||||
|
c.client_id,
|
||||||
|
c.campaign_id AS external_campaign_id,
|
||||||
|
ag.ad_group_id AS external_ad_group_id,
|
||||||
|
cl.google_ads_customer_id
|
||||||
|
FROM campaign_search_terms AS st
|
||||||
|
INNER JOIN campaigns AS c ON c.id = st.campaign_id
|
||||||
|
INNER JOIN clients AS cl ON cl.id = c.client_id
|
||||||
|
LEFT JOIN campaign_ad_groups AS ag ON ag.id = st.ad_group_id
|
||||||
|
WHERE st.id = :search_term_row_id
|
||||||
|
LIMIT 1',
|
||||||
|
[ ':search_term_row_id' => (int) $search_term_row_id ]
|
||||||
|
) -> fetch( \PDO::FETCH_ASSOC );
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function upsert_campaign_negative_keyword( $campaign_id, $ad_group_id, $scope, $keyword_text, $match_type )
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
$campaign_id = (int) $campaign_id;
|
||||||
|
$ad_group_id = $ad_group_id !== null ? (int) $ad_group_id : null;
|
||||||
|
$scope = $scope === 'campaign' ? 'campaign' : 'ad_group';
|
||||||
|
$keyword_text = trim( (string) $keyword_text );
|
||||||
|
$match_type = strtoupper( trim( (string) $match_type ) );
|
||||||
|
|
||||||
|
if ( $campaign_id <= 0 || $keyword_text === '' )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$existing = $mdb -> query(
|
||||||
|
'SELECT id
|
||||||
|
FROM campaign_negative_keywords
|
||||||
|
WHERE campaign_id = :campaign_id
|
||||||
|
AND ( ( :ad_group_id IS NULL AND ad_group_id IS NULL ) OR ad_group_id = :ad_group_id )
|
||||||
|
AND scope = :scope
|
||||||
|
AND LOWER(keyword_text) = LOWER(:keyword_text)
|
||||||
|
AND UPPER(COALESCE(match_type, \'\')) = :match_type
|
||||||
|
LIMIT 1',
|
||||||
|
[
|
||||||
|
':campaign_id' => $campaign_id,
|
||||||
|
':ad_group_id' => $ad_group_id,
|
||||||
|
':scope' => $scope,
|
||||||
|
':keyword_text' => $keyword_text,
|
||||||
|
':match_type' => $match_type
|
||||||
|
]
|
||||||
|
) -> fetchColumn();
|
||||||
|
|
||||||
|
if ( $existing )
|
||||||
|
{
|
||||||
|
return (int) $existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mdb -> insert( 'campaign_negative_keywords', [
|
||||||
|
'campaign_id' => $campaign_id,
|
||||||
|
'ad_group_id' => $ad_group_id,
|
||||||
|
'scope' => $scope,
|
||||||
|
'keyword_text' => $keyword_text,
|
||||||
|
'match_type' => $match_type,
|
||||||
|
'date_sync' => date( 'Y-m-d' )
|
||||||
|
] );
|
||||||
|
|
||||||
|
return (int) $mdb -> id();
|
||||||
|
}
|
||||||
|
|
||||||
static public function delete_campaign( $campaign_id )
|
static public function delete_campaign( $campaign_id )
|
||||||
{
|
{
|
||||||
global $mdb;
|
global $mdb;
|
||||||
@@ -47,4 +218,4 @@ class Campaigns
|
|||||||
global $mdb;
|
global $mdb;
|
||||||
return $mdb -> delete( 'campaigns_history', [ 'id' => $history_id ] );
|
return $mdb -> delete( 'campaigns_history', [ 'id' => $history_id ] );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,8 +180,323 @@ class GoogleAdsApi
|
|||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function mutate( $customer_id, $mutate_operations, $partial_failure = false )
|
||||||
|
{
|
||||||
|
$access_token = $this -> get_access_token();
|
||||||
|
if ( !$access_token ) return false;
|
||||||
|
|
||||||
|
$customer_id = str_replace( '-', '', $customer_id );
|
||||||
|
|
||||||
|
$url = self::$ADS_BASE_URL . '/' . self::$API_VERSION
|
||||||
|
. '/customers/' . $customer_id . '/googleAds:mutate';
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
'Authorization: Bearer ' . $access_token,
|
||||||
|
'developer-token: ' . $this -> developer_token,
|
||||||
|
'Content-Type: application/json',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ( !empty( $this -> manager_account_id ) )
|
||||||
|
{
|
||||||
|
$headers[] = 'login-customer-id: ' . str_replace( '-', '', $this -> manager_account_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = [
|
||||||
|
'mutateOperations' => array_values( $mutate_operations ),
|
||||||
|
'partialFailure' => (bool) $partial_failure
|
||||||
|
];
|
||||||
|
|
||||||
|
$ch = curl_init( $url );
|
||||||
|
curl_setopt_array( $ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_HTTPHEADER => $headers,
|
||||||
|
CURLOPT_POSTFIELDS => json_encode( $payload ),
|
||||||
|
CURLOPT_SSL_VERIFYPEER => true,
|
||||||
|
CURLOPT_TIMEOUT => 120,
|
||||||
|
] );
|
||||||
|
|
||||||
|
$response = curl_exec( $ch );
|
||||||
|
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||||
|
$error = curl_error( $ch );
|
||||||
|
curl_close( $ch );
|
||||||
|
|
||||||
|
if ( $http_code !== 200 || !$response )
|
||||||
|
{
|
||||||
|
self::set_setting( 'google_ads_last_error', 'mutate failed: HTTP ' . $http_code . ' | ' . $error . ' | ' . substr( (string) $response, 0, 1000 ) );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode( $response, true );
|
||||||
|
if ( !is_array( $data ) )
|
||||||
|
{
|
||||||
|
self::set_setting( 'google_ads_last_error', 'mutate failed: niepoprawna odpowiedz JSON' );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::set_setting( 'google_ads_last_error', null );
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add_negative_keyword_to_ad_group( $customer_id, $ad_group_id, $keyword_text, $match_type = 'PHRASE' )
|
||||||
|
{
|
||||||
|
$customer_id = trim( str_replace( '-', '', (string) $customer_id ) );
|
||||||
|
$ad_group_id = trim( (string) $ad_group_id );
|
||||||
|
$keyword_text = trim( (string) $keyword_text );
|
||||||
|
$match_type = strtoupper( trim( (string) $match_type ) );
|
||||||
|
|
||||||
|
if ( $customer_id === '' || $ad_group_id === '' || $keyword_text === '' )
|
||||||
|
{
|
||||||
|
self::set_setting( 'google_ads_last_error', 'Brak wymaganych danych do dodania frazy wykluczajacej.' );
|
||||||
|
return [ 'success' => false, 'duplicate' => false ];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !in_array( $match_type, [ 'PHRASE', 'EXACT', 'BROAD' ], true ) )
|
||||||
|
{
|
||||||
|
$match_type = 'PHRASE';
|
||||||
|
}
|
||||||
|
|
||||||
|
$operation = [
|
||||||
|
'adGroupCriterionOperation' => [
|
||||||
|
'create' => [
|
||||||
|
'adGroup' => 'customers/' . $customer_id . '/adGroups/' . $ad_group_id,
|
||||||
|
'negative' => true,
|
||||||
|
'keyword' => [
|
||||||
|
'text' => $keyword_text,
|
||||||
|
'matchType' => $match_type
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = $this -> mutate( $customer_id, [ $operation ] );
|
||||||
|
|
||||||
|
if ( $result === false )
|
||||||
|
{
|
||||||
|
$last_error = (string) self::get_setting( 'google_ads_last_error' );
|
||||||
|
$is_duplicate = stripos( $last_error, 'DUPLICATE' ) !== false
|
||||||
|
|| stripos( $last_error, 'already exists' ) !== false;
|
||||||
|
|
||||||
|
if ( $is_duplicate )
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'duplicate' => true,
|
||||||
|
'verification' => $this -> verify_negative_keyword_exists( $customer_id, 'ad_group', $keyword_text, $match_type, null, $ad_group_id )
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ 'success' => false, 'duplicate' => false, 'sent_operation' => $operation ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'duplicate' => false,
|
||||||
|
'response' => $result,
|
||||||
|
'sent_operation' => $operation,
|
||||||
|
'verification' => $this -> verify_negative_keyword_exists( $customer_id, 'ad_group', $keyword_text, $match_type, null, $ad_group_id )
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function add_negative_keyword_to_campaign( $customer_id, $campaign_id, $keyword_text, $match_type = 'PHRASE' )
|
||||||
|
{
|
||||||
|
$customer_id = trim( str_replace( '-', '', (string) $customer_id ) );
|
||||||
|
$campaign_id = trim( (string) $campaign_id );
|
||||||
|
$keyword_text = trim( (string) $keyword_text );
|
||||||
|
$match_type = strtoupper( trim( (string) $match_type ) );
|
||||||
|
|
||||||
|
if ( $customer_id === '' || $campaign_id === '' || $keyword_text === '' )
|
||||||
|
{
|
||||||
|
self::set_setting( 'google_ads_last_error', 'Brak wymaganych danych do dodania frazy wykluczajacej.' );
|
||||||
|
return [ 'success' => false, 'duplicate' => false ];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !in_array( $match_type, [ 'PHRASE', 'EXACT', 'BROAD' ], true ) )
|
||||||
|
{
|
||||||
|
$match_type = 'PHRASE';
|
||||||
|
}
|
||||||
|
|
||||||
|
$operation = [
|
||||||
|
'campaignCriterionOperation' => [
|
||||||
|
'create' => [
|
||||||
|
'campaign' => 'customers/' . $customer_id . '/campaigns/' . $campaign_id,
|
||||||
|
'negative' => true,
|
||||||
|
'keyword' => [
|
||||||
|
'text' => $keyword_text,
|
||||||
|
'matchType' => $match_type
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = $this -> mutate( $customer_id, [ $operation ] );
|
||||||
|
|
||||||
|
if ( $result === false )
|
||||||
|
{
|
||||||
|
$last_error = (string) self::get_setting( 'google_ads_last_error' );
|
||||||
|
$is_duplicate = stripos( $last_error, 'DUPLICATE' ) !== false
|
||||||
|
|| stripos( $last_error, 'already exists' ) !== false;
|
||||||
|
|
||||||
|
if ( $is_duplicate )
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'duplicate' => true,
|
||||||
|
'verification' => $this -> verify_negative_keyword_exists( $customer_id, 'campaign', $keyword_text, $match_type, $campaign_id, null )
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [ 'success' => false, 'duplicate' => false, 'sent_operation' => $operation ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'duplicate' => false,
|
||||||
|
'response' => $result,
|
||||||
|
'sent_operation' => $operation,
|
||||||
|
'verification' => $this -> verify_negative_keyword_exists( $customer_id, 'campaign', $keyword_text, $match_type, $campaign_id, null )
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function gaql_escape( $value )
|
||||||
|
{
|
||||||
|
return str_replace( [ '\\', '\'' ], [ '\\\\', '\\\'' ], (string) $value );
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verify_negative_keyword_exists( $customer_id, $scope, $keyword_text, $match_type, $campaign_id = null, $ad_group_id = null )
|
||||||
|
{
|
||||||
|
$customer_id = trim( str_replace( '-', '', (string) $customer_id ) );
|
||||||
|
$scope = $scope === 'campaign' ? 'campaign' : 'ad_group';
|
||||||
|
$match_type = strtoupper( trim( (string) $match_type ) );
|
||||||
|
$keyword_text_escaped = $this -> gaql_escape( trim( (string) $keyword_text ) );
|
||||||
|
|
||||||
|
if ( $scope === 'campaign' )
|
||||||
|
{
|
||||||
|
$campaign_id = trim( (string) $campaign_id );
|
||||||
|
if ( $campaign_id === '' || $keyword_text_escaped === '' )
|
||||||
|
{
|
||||||
|
return [ 'found' => false, 'scope' => $scope, 'rows' => [], 'error' => 'Brak danych do weryfikacji.' ];
|
||||||
|
}
|
||||||
|
|
||||||
|
$gaql = "SELECT "
|
||||||
|
. "campaign_criterion.resource_name, "
|
||||||
|
. "campaign.id, "
|
||||||
|
. "campaign_criterion.keyword.text, "
|
||||||
|
. "campaign_criterion.keyword.match_type "
|
||||||
|
. "FROM campaign_criterion "
|
||||||
|
. "WHERE campaign.id = " . $campaign_id . " "
|
||||||
|
. "AND campaign_criterion.type = 'KEYWORD' "
|
||||||
|
. "AND campaign_criterion.negative = TRUE "
|
||||||
|
. "AND campaign_criterion.keyword.text = '" . $keyword_text_escaped . "' "
|
||||||
|
. "AND campaign_criterion.keyword.match_type = " . $match_type . " "
|
||||||
|
. "LIMIT 5";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$ad_group_id = trim( (string) $ad_group_id );
|
||||||
|
if ( $ad_group_id === '' || $keyword_text_escaped === '' )
|
||||||
|
{
|
||||||
|
return [ 'found' => false, 'scope' => $scope, 'rows' => [], 'error' => 'Brak danych do weryfikacji.' ];
|
||||||
|
}
|
||||||
|
|
||||||
|
$gaql = "SELECT "
|
||||||
|
. "ad_group_criterion.resource_name, "
|
||||||
|
. "campaign.id, "
|
||||||
|
. "ad_group.id, "
|
||||||
|
. "ad_group_criterion.keyword.text, "
|
||||||
|
. "ad_group_criterion.keyword.match_type "
|
||||||
|
. "FROM ad_group_criterion "
|
||||||
|
. "WHERE ad_group.id = " . $ad_group_id . " "
|
||||||
|
. "AND ad_group_criterion.type = 'KEYWORD' "
|
||||||
|
. "AND ad_group_criterion.negative = TRUE "
|
||||||
|
. "AND ad_group_criterion.keyword.text = '" . $keyword_text_escaped . "' "
|
||||||
|
. "AND ad_group_criterion.keyword.match_type = " . $match_type . " "
|
||||||
|
. "LIMIT 5";
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
$last_error = null;
|
||||||
|
for ( $i = 0; $i < 3; $i++ )
|
||||||
|
{
|
||||||
|
$result = $this -> search_stream( $customer_id, $gaql );
|
||||||
|
if ( is_array( $result ) )
|
||||||
|
{
|
||||||
|
$rows = $result;
|
||||||
|
if ( count( $rows ) > 0 )
|
||||||
|
{
|
||||||
|
return [ 'found' => true, 'scope' => $scope, 'rows' => $rows ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$last_error = (string) self::get_setting( 'google_ads_last_error' );
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep( 400000 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'found' => count( $rows ) > 0,
|
||||||
|
'scope' => $scope,
|
||||||
|
'rows' => $rows,
|
||||||
|
'error' => $last_error
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// --- Kampanie: dane 30-dniowe ---
|
// --- Kampanie: dane 30-dniowe ---
|
||||||
|
|
||||||
|
public function get_products_for_date( $customer_id, $date )
|
||||||
|
{
|
||||||
|
$date = date( 'Y-m-d', strtotime( $date ) );
|
||||||
|
|
||||||
|
$gaql = "SELECT "
|
||||||
|
. "segments.date, "
|
||||||
|
. "segments.product_item_id, "
|
||||||
|
. "segments.product_title, "
|
||||||
|
. "metrics.impressions, "
|
||||||
|
. "metrics.clicks, "
|
||||||
|
. "metrics.cost_micros, "
|
||||||
|
. "metrics.conversions, "
|
||||||
|
. "metrics.conversions_value "
|
||||||
|
. "FROM shopping_performance_view "
|
||||||
|
. "WHERE segments.date = '" . $date . "'";
|
||||||
|
|
||||||
|
$results = $this -> search_stream( $customer_id, $gaql );
|
||||||
|
if ( $results === false ) return false;
|
||||||
|
|
||||||
|
$products = [];
|
||||||
|
|
||||||
|
foreach ( $results as $row )
|
||||||
|
{
|
||||||
|
$offer_id = trim( (string) ( $row['segments']['productItemId'] ?? '' ) );
|
||||||
|
if ( $offer_id === '' )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !isset( $products[ $offer_id ] ) )
|
||||||
|
{
|
||||||
|
$products[ $offer_id ] = [
|
||||||
|
'OfferId' => $offer_id,
|
||||||
|
'ProductTitle' => (string) ( $row['segments']['productTitle'] ?? $offer_id ),
|
||||||
|
'Impressions' => 0,
|
||||||
|
'Clicks' => 0,
|
||||||
|
'Cost' => 0.0,
|
||||||
|
'Conversions' => 0.0,
|
||||||
|
'ConversionValue' => 0.0
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$products[ $offer_id ]['Impressions'] += (int) ( $row['metrics']['impressions'] ?? 0 );
|
||||||
|
$products[ $offer_id ]['Clicks'] += (int) ( $row['metrics']['clicks'] ?? 0 );
|
||||||
|
$products[ $offer_id ]['Cost'] += (float) ( $row['metrics']['costMicros'] ?? 0 ) / 1000000;
|
||||||
|
$products[ $offer_id ]['Conversions'] += (float) ( $row['metrics']['conversions'] ?? 0 );
|
||||||
|
$products[ $offer_id ]['ConversionValue'] += (float) ( $row['metrics']['conversionsValue'] ?? 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values( $products );
|
||||||
|
}
|
||||||
|
|
||||||
public function get_campaigns_30_days( $customer_id )
|
public function get_campaigns_30_days( $customer_id )
|
||||||
{
|
{
|
||||||
$gaql = "SELECT "
|
$gaql = "SELECT "
|
||||||
@@ -264,11 +579,296 @@ class GoogleAdsApi
|
|||||||
$value = (float) ( $row['metrics']['conversionsValue'] ?? 0 );
|
$value = (float) ( $row['metrics']['conversionsValue'] ?? 0 );
|
||||||
|
|
||||||
$campaigns[] = [
|
$campaigns[] = [
|
||||||
'campaign_id' => $cid,
|
'campaign_id' => $cid,
|
||||||
'roas_all_time' => ( $cost > 0 ) ? round( ( $value / $cost ) * 100, 2 ) : 0,
|
'cost_all_time' => $cost,
|
||||||
|
'conversion_value_all_time' => $value,
|
||||||
|
'roas_all_time' => ( $cost > 0 ) ? round( ( $value / $cost ) * 100, 2 ) : 0,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $campaigns;
|
return $campaigns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function get_ad_groups_30_days( $customer_id )
|
||||||
|
{
|
||||||
|
$gaql = "SELECT "
|
||||||
|
. "campaign.id, "
|
||||||
|
. "ad_group.id, "
|
||||||
|
. "ad_group.name, "
|
||||||
|
. "metrics.impressions, "
|
||||||
|
. "metrics.clicks, "
|
||||||
|
. "metrics.cost_micros, "
|
||||||
|
. "metrics.conversions, "
|
||||||
|
. "metrics.conversions_value "
|
||||||
|
. "FROM ad_group "
|
||||||
|
. "WHERE campaign.status != 'REMOVED' "
|
||||||
|
. "AND ad_group.status != 'REMOVED' "
|
||||||
|
. "AND segments.date DURING LAST_30_DAYS";
|
||||||
|
|
||||||
|
$results = $this -> search_stream( $customer_id, $gaql );
|
||||||
|
if ( $results === false ) return false;
|
||||||
|
|
||||||
|
return $this -> aggregate_ad_groups( $results );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_ad_groups_all_time( $customer_id )
|
||||||
|
{
|
||||||
|
$gaql = "SELECT "
|
||||||
|
. "campaign.id, "
|
||||||
|
. "ad_group.id, "
|
||||||
|
. "ad_group.name, "
|
||||||
|
. "metrics.impressions, "
|
||||||
|
. "metrics.clicks, "
|
||||||
|
. "metrics.cost_micros, "
|
||||||
|
. "metrics.conversions, "
|
||||||
|
. "metrics.conversions_value "
|
||||||
|
. "FROM ad_group "
|
||||||
|
. "WHERE campaign.status != 'REMOVED' "
|
||||||
|
. "AND ad_group.status != 'REMOVED'";
|
||||||
|
|
||||||
|
$results = $this -> search_stream( $customer_id, $gaql );
|
||||||
|
if ( $results === false ) return false;
|
||||||
|
|
||||||
|
return $this -> aggregate_ad_groups( $results );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_search_terms_30_days( $customer_id )
|
||||||
|
{
|
||||||
|
$gaql = "SELECT "
|
||||||
|
. "campaign.id, "
|
||||||
|
. "ad_group.id, "
|
||||||
|
. "ad_group.name, "
|
||||||
|
. "search_term_view.search_term, "
|
||||||
|
. "metrics.impressions, "
|
||||||
|
. "metrics.clicks, "
|
||||||
|
. "metrics.cost_micros, "
|
||||||
|
. "metrics.conversions, "
|
||||||
|
. "metrics.conversions_value "
|
||||||
|
. "FROM search_term_view "
|
||||||
|
. "WHERE campaign.status != 'REMOVED' "
|
||||||
|
. "AND ad_group.status != 'REMOVED' "
|
||||||
|
. "AND metrics.clicks > 0 "
|
||||||
|
. "AND segments.date DURING LAST_30_DAYS";
|
||||||
|
|
||||||
|
$results = $this -> search_stream( $customer_id, $gaql );
|
||||||
|
if ( $results === false ) return false;
|
||||||
|
|
||||||
|
return $this -> aggregate_search_terms( $results );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_search_terms_all_time( $customer_id )
|
||||||
|
{
|
||||||
|
$gaql = "SELECT "
|
||||||
|
. "campaign.id, "
|
||||||
|
. "ad_group.id, "
|
||||||
|
. "ad_group.name, "
|
||||||
|
. "search_term_view.search_term, "
|
||||||
|
. "metrics.impressions, "
|
||||||
|
. "metrics.clicks, "
|
||||||
|
. "metrics.cost_micros, "
|
||||||
|
. "metrics.conversions, "
|
||||||
|
. "metrics.conversions_value "
|
||||||
|
. "FROM search_term_view "
|
||||||
|
. "WHERE campaign.status != 'REMOVED' "
|
||||||
|
. "AND ad_group.status != 'REMOVED' "
|
||||||
|
. "AND metrics.clicks > 0";
|
||||||
|
|
||||||
|
$results = $this -> search_stream( $customer_id, $gaql );
|
||||||
|
if ( $results === false ) return false;
|
||||||
|
|
||||||
|
return $this -> aggregate_search_terms( $results );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_negative_keywords( $customer_id )
|
||||||
|
{
|
||||||
|
$campaign_gaql = "SELECT "
|
||||||
|
. "campaign.id, "
|
||||||
|
. "campaign_criterion.keyword.text, "
|
||||||
|
. "campaign_criterion.keyword.match_type "
|
||||||
|
. "FROM campaign_criterion "
|
||||||
|
. "WHERE campaign.status != 'REMOVED' "
|
||||||
|
. "AND campaign_criterion.type = 'KEYWORD' "
|
||||||
|
. "AND campaign_criterion.negative = TRUE";
|
||||||
|
|
||||||
|
$ad_group_gaql = "SELECT "
|
||||||
|
. "campaign.id, "
|
||||||
|
. "ad_group.id, "
|
||||||
|
. "ad_group_criterion.keyword.text, "
|
||||||
|
. "ad_group_criterion.keyword.match_type "
|
||||||
|
. "FROM ad_group_criterion "
|
||||||
|
. "WHERE campaign.status != 'REMOVED' "
|
||||||
|
. "AND ad_group.status != 'REMOVED' "
|
||||||
|
. "AND ad_group_criterion.type = 'KEYWORD' "
|
||||||
|
. "AND ad_group_criterion.negative = TRUE";
|
||||||
|
|
||||||
|
$campaign_results = $this -> search_stream( $customer_id, $campaign_gaql );
|
||||||
|
if ( $campaign_results === false ) return false;
|
||||||
|
|
||||||
|
$ad_group_results = $this -> search_stream( $customer_id, $ad_group_gaql );
|
||||||
|
if ( $ad_group_results === false ) return false;
|
||||||
|
|
||||||
|
$negatives = [];
|
||||||
|
$seen = [];
|
||||||
|
|
||||||
|
foreach ( $campaign_results as $row )
|
||||||
|
{
|
||||||
|
$campaign_id = $row['campaign']['id'] ?? null;
|
||||||
|
$text = trim( (string) ( $row['campaignCriterion']['keyword']['text'] ?? '' ) );
|
||||||
|
$match_type = (string) ( $row['campaignCriterion']['keyword']['matchType'] ?? '' );
|
||||||
|
|
||||||
|
if ( !$campaign_id || $text === '' )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = 'campaign|' . $campaign_id . '||' . strtolower( $text ) . '|' . $match_type;
|
||||||
|
if ( isset( $seen[ $key ] ) )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$seen[ $key ] = true;
|
||||||
|
$negatives[] = [
|
||||||
|
'scope' => 'campaign',
|
||||||
|
'campaign_id' => (int) $campaign_id,
|
||||||
|
'ad_group_id' => null,
|
||||||
|
'keyword_text' => $text,
|
||||||
|
'match_type' => $match_type
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $ad_group_results as $row )
|
||||||
|
{
|
||||||
|
$campaign_id = $row['campaign']['id'] ?? null;
|
||||||
|
$ad_group_id = $row['adGroup']['id'] ?? null;
|
||||||
|
$text = trim( (string) ( $row['adGroupCriterion']['keyword']['text'] ?? '' ) );
|
||||||
|
$match_type = (string) ( $row['adGroupCriterion']['keyword']['matchType'] ?? '' );
|
||||||
|
|
||||||
|
if ( !$campaign_id || !$ad_group_id || $text === '' )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = 'ad_group|' . $campaign_id . '|' . $ad_group_id . '|' . strtolower( $text ) . '|' . $match_type;
|
||||||
|
if ( isset( $seen[ $key ] ) )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$seen[ $key ] = true;
|
||||||
|
$negatives[] = [
|
||||||
|
'scope' => 'ad_group',
|
||||||
|
'campaign_id' => (int) $campaign_id,
|
||||||
|
'ad_group_id' => (int) $ad_group_id,
|
||||||
|
'keyword_text' => $text,
|
||||||
|
'match_type' => $match_type
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $negatives;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function aggregate_ad_groups( $results )
|
||||||
|
{
|
||||||
|
$ad_groups = [];
|
||||||
|
|
||||||
|
foreach ( $results as $row )
|
||||||
|
{
|
||||||
|
$campaign_id = $row['campaign']['id'] ?? null;
|
||||||
|
$ad_group_id = $row['adGroup']['id'] ?? null;
|
||||||
|
|
||||||
|
if ( !$campaign_id || !$ad_group_id )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = $campaign_id . '|' . $ad_group_id;
|
||||||
|
|
||||||
|
if ( !isset( $ad_groups[ $key ] ) )
|
||||||
|
{
|
||||||
|
$ad_groups[ $key ] = [
|
||||||
|
'campaign_id' => (int) $campaign_id,
|
||||||
|
'ad_group_id' => (int) $ad_group_id,
|
||||||
|
'ad_group_name' => (string) ( $row['adGroup']['name'] ?? '' ),
|
||||||
|
'impressions' => 0,
|
||||||
|
'clicks' => 0,
|
||||||
|
'cost' => 0.0,
|
||||||
|
'conversions' => 0.0,
|
||||||
|
'conversion_value' => 0.0,
|
||||||
|
'roas' => 0.0
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ad_groups[ $key ]['impressions'] += (int) ( $row['metrics']['impressions'] ?? 0 );
|
||||||
|
$ad_groups[ $key ]['clicks'] += (int) ( $row['metrics']['clicks'] ?? 0 );
|
||||||
|
$ad_groups[ $key ]['cost'] += (float) ( $row['metrics']['costMicros'] ?? 0 ) / 1000000;
|
||||||
|
$ad_groups[ $key ]['conversions'] += (float) ( $row['metrics']['conversions'] ?? 0 );
|
||||||
|
$ad_groups[ $key ]['conversion_value'] += (float) ( $row['metrics']['conversionsValue'] ?? 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $ad_groups as &$ad_group )
|
||||||
|
{
|
||||||
|
$ad_group['roas'] = ( $ad_group['cost'] > 0 )
|
||||||
|
? round( ( $ad_group['conversion_value'] / $ad_group['cost'] ) * 100, 2 )
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values( $ad_groups );
|
||||||
|
}
|
||||||
|
|
||||||
|
private function aggregate_search_terms( $results )
|
||||||
|
{
|
||||||
|
$terms = [];
|
||||||
|
|
||||||
|
foreach ( $results as $row )
|
||||||
|
{
|
||||||
|
$campaign_id = $row['campaign']['id'] ?? null;
|
||||||
|
$ad_group_id = $row['adGroup']['id'] ?? null;
|
||||||
|
$search_term = trim( (string) ( $row['searchTermView']['searchTerm'] ?? '' ) );
|
||||||
|
|
||||||
|
if ( !$campaign_id || !$ad_group_id || $search_term === '' )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = $campaign_id . '|' . $ad_group_id . '|' . strtolower( $search_term );
|
||||||
|
|
||||||
|
if ( !isset( $terms[ $key ] ) )
|
||||||
|
{
|
||||||
|
$terms[ $key ] = [
|
||||||
|
'campaign_id' => (int) $campaign_id,
|
||||||
|
'ad_group_id' => (int) $ad_group_id,
|
||||||
|
'ad_group_name' => (string) ( $row['adGroup']['name'] ?? '' ),
|
||||||
|
'search_term' => $search_term,
|
||||||
|
'impressions' => 0,
|
||||||
|
'clicks' => 0,
|
||||||
|
'cost' => 0.0,
|
||||||
|
'conversions' => 0.0,
|
||||||
|
'conversion_value' => 0.0,
|
||||||
|
'roas' => 0.0
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$terms[ $key ]['impressions'] += (int) ( $row['metrics']['impressions'] ?? 0 );
|
||||||
|
$terms[ $key ]['clicks'] += (int) ( $row['metrics']['clicks'] ?? 0 );
|
||||||
|
$terms[ $key ]['cost'] += (float) ( $row['metrics']['costMicros'] ?? 0 ) / 1000000;
|
||||||
|
$terms[ $key ]['conversions'] += (float) ( $row['metrics']['conversions'] ?? 0 );
|
||||||
|
$terms[ $key ]['conversion_value'] += (float) ( $row['metrics']['conversionsValue'] ?? 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $terms as $key => &$term )
|
||||||
|
{
|
||||||
|
if ( (int) $term['clicks'] <= 0 )
|
||||||
|
{
|
||||||
|
unset( $terms[ $key ] );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$term['roas'] = ( $term['cost'] > 0 )
|
||||||
|
? round( ( $term['conversion_value'] / $term['cost'] ) * 100, 2 )
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values( $terms );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
344
docs/database.sql
Normal file
344
docs/database.sql
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
-- --------------------------------------------------------
|
||||||
|
-- Host: host700513.hostido.net.pl
|
||||||
|
-- Wersja serwera: 10.11.15-MariaDB-cll-lve - MariaDB Server
|
||||||
|
-- Serwer OS: Linux
|
||||||
|
-- HeidiSQL Wersja: 12.6.0.6765
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET NAMES utf8 */;
|
||||||
|
/*!50503 SET NAMES utf8mb4 */;
|
||||||
|
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||||
|
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||||
|
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||||
|
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||||
|
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.campaigns
|
||||||
|
CREATE TABLE IF NOT EXISTS `campaigns` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`client_id` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`campaign_id` bigint(20) NOT NULL DEFAULT 0,
|
||||||
|
`campaign_name` varchar(255) NOT NULL DEFAULT '0',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `client_id` (`client_id`),
|
||||||
|
CONSTRAINT `FK__clients` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=123 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.campaigns_comments
|
||||||
|
CREATE TABLE IF NOT EXISTS `campaigns_comments` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`campaign_id` int(11) NOT NULL,
|
||||||
|
`comment` text NOT NULL,
|
||||||
|
`date_add` date NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
KEY `campaign_id` (`campaign_id`),
|
||||||
|
CONSTRAINT `FK_campaigns_comments_campaigns` FOREIGN KEY (`campaign_id`) REFERENCES `campaigns` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.campaigns_history
|
||||||
|
CREATE TABLE IF NOT EXISTS `campaigns_history` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`campaign_id` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`roas_30_days` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`roas_all_time` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`budget` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`money_spent` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversion_value` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`bidding_strategy` text DEFAULT NULL,
|
||||||
|
`date_add` date NOT NULL DEFAULT '0000-00-00',
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
KEY `offer_id` (`campaign_id`) USING BTREE,
|
||||||
|
CONSTRAINT `FK_campaigns_history_campaigns` FOREIGN KEY (`campaign_id`) REFERENCES `campaigns` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=4400 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.clients
|
||||||
|
CREATE TABLE IF NOT EXISTS `clients` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(255) NOT NULL DEFAULT '0',
|
||||||
|
`google_ads_customer_id` varchar(20) DEFAULT NULL,
|
||||||
|
`google_ads_start_date` date DEFAULT NULL,
|
||||||
|
`deleted` int(11) DEFAULT 0,
|
||||||
|
`bestseller_min_roas` int(11) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.phrases
|
||||||
|
CREATE TABLE IF NOT EXISTS `phrases` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`client_id` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`phrase` varchar(255) NOT NULL DEFAULT '0',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `FK_phrases_clients` (`client_id`),
|
||||||
|
CONSTRAINT `FK_phrases_clients` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=5512 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.phrases_history
|
||||||
|
CREATE TABLE IF NOT EXISTS `phrases_history` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`phrase_id` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`impressions` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`clicks` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`cost` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions_value` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`date_add` date NOT NULL DEFAULT '0000-00-00',
|
||||||
|
`updated` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`deleted` int(11) DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
KEY `offer_id` (`phrase_id`) USING BTREE,
|
||||||
|
CONSTRAINT `FK_phrases_history_phrases` FOREIGN KEY (`phrase_id`) REFERENCES `phrases` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=13088 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.phrases_history_30
|
||||||
|
CREATE TABLE IF NOT EXISTS `phrases_history_30` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`phrase_id` int(11) NOT NULL,
|
||||||
|
`impressions` int(11) NOT NULL,
|
||||||
|
`clicks` int(11) NOT NULL,
|
||||||
|
`cost` decimal(20,6) NOT NULL,
|
||||||
|
`conversions` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions_value` decimal(20,6) NOT NULL,
|
||||||
|
`roas` decimal(20,6) NOT NULL,
|
||||||
|
`date_add` date NOT NULL DEFAULT '0000-00-00',
|
||||||
|
`deleted` int(11) DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
KEY `offer_id` (`phrase_id`) USING BTREE,
|
||||||
|
CONSTRAINT `FK_phrases_history_30_phrases` FOREIGN KEY (`phrase_id`) REFERENCES `phrases` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=1795 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.phrases_temp
|
||||||
|
CREATE TABLE IF NOT EXISTS `phrases_temp` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`phrase_id` int(11) DEFAULT NULL,
|
||||||
|
`phrase` varchar(255) DEFAULT NULL,
|
||||||
|
`impressions` int(11) DEFAULT NULL,
|
||||||
|
`clicks` int(11) DEFAULT NULL,
|
||||||
|
`cost` decimal(20,6) DEFAULT NULL,
|
||||||
|
`conversions` decimal(20,6) DEFAULT NULL,
|
||||||
|
`conversions_value` decimal(20,6) DEFAULT NULL,
|
||||||
|
`cpc` decimal(20,6) DEFAULT NULL,
|
||||||
|
`roas` decimal(20,0) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
KEY `offer_id` (`phrase_id`) USING BTREE,
|
||||||
|
CONSTRAINT `FK_phrases_temp_phrases` FOREIGN KEY (`phrase_id`) REFERENCES `phrases` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=353973 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.products
|
||||||
|
CREATE TABLE IF NOT EXISTS `products` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`client_id` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`offer_id` varchar(50) NOT NULL DEFAULT '0',
|
||||||
|
`name` varchar(255) NOT NULL DEFAULT '0',
|
||||||
|
`min_roas` int(11) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `FK_offers_clients` (`client_id`),
|
||||||
|
CONSTRAINT `FK_offers_clients` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=5927 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.products_comments
|
||||||
|
CREATE TABLE IF NOT EXISTS `products_comments` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`product_id` int(11) NOT NULL,
|
||||||
|
`comment` text NOT NULL,
|
||||||
|
`date_add` date NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `product_id` (`product_id`) USING BTREE,
|
||||||
|
CONSTRAINT `FK_products_comments_products` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=118 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.products_data
|
||||||
|
CREATE TABLE IF NOT EXISTS `products_data` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`product_id` int(11) DEFAULT NULL,
|
||||||
|
`custom_label_4` varchar(255) DEFAULT NULL,
|
||||||
|
`custom_label_3` varchar(255) DEFAULT NULL,
|
||||||
|
`title` varchar(255) DEFAULT NULL,
|
||||||
|
`description` text DEFAULT NULL,
|
||||||
|
`google_product_category` text DEFAULT NULL,
|
||||||
|
`product_url` varchar(500) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `product_id` (`product_id`) USING BTREE,
|
||||||
|
CONSTRAINT `FK_products_data_products` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=118 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.products_history
|
||||||
|
CREATE TABLE IF NOT EXISTS `products_history` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`product_id` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`impressions` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`clicks` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`ctr` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`cost` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions_value` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`date_add` date NOT NULL DEFAULT '0000-00-00',
|
||||||
|
`updated` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`deleted` int(11) DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`) USING BTREE,
|
||||||
|
KEY `product_id` (`product_id`) USING BTREE,
|
||||||
|
CONSTRAINT `FK_products_history_products` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=63549 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.products_history_30
|
||||||
|
CREATE TABLE IF NOT EXISTS `products_history_30` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`product_id` int(11) NOT NULL,
|
||||||
|
`impressions` int(11) NOT NULL,
|
||||||
|
`clicks` int(11) NOT NULL,
|
||||||
|
`ctr` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`cost` decimal(20,6) NOT NULL,
|
||||||
|
`conversions` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions_value` decimal(20,6) NOT NULL,
|
||||||
|
`roas` decimal(20,6) NOT NULL,
|
||||||
|
`roas_all_time` decimal(20,6) NOT NULL,
|
||||||
|
`date_add` date NOT NULL DEFAULT '0000-00-00',
|
||||||
|
`deleted` int(11) DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `product_id` (`product_id`) USING BTREE,
|
||||||
|
CONSTRAINT `FK_products_history_30_products` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=27655 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.products_temp
|
||||||
|
CREATE TABLE IF NOT EXISTS `products_temp` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`product_id` int(11) DEFAULT NULL,
|
||||||
|
`name` varchar(255) DEFAULT NULL,
|
||||||
|
`impressions` int(11) DEFAULT NULL,
|
||||||
|
`impressions_30` int(11) DEFAULT NULL,
|
||||||
|
`clicks` int(11) DEFAULT NULL,
|
||||||
|
`clicks_30` int(11) DEFAULT NULL,
|
||||||
|
`ctr` decimal(20,6) DEFAULT NULL,
|
||||||
|
`cost` decimal(20,6) DEFAULT NULL,
|
||||||
|
`conversions` decimal(20,6) DEFAULT NULL,
|
||||||
|
`conversions_value` decimal(20,6) DEFAULT NULL,
|
||||||
|
`cpc` decimal(20,6) DEFAULT NULL,
|
||||||
|
`roas` decimal(20,0) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `product_id` (`product_id`) USING BTREE,
|
||||||
|
CONSTRAINT `FK_products_temp_products` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=298845 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.settings
|
||||||
|
CREATE TABLE IF NOT EXISTS `settings` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`setting_key` varchar(100) NOT NULL,
|
||||||
|
`setting_value` text DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_setting_key` (`setting_key`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
-- Zrzut struktury tabela host700513_adspro.users
|
||||||
|
CREATE TABLE IF NOT EXISTS `users` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`email` varchar(255) NOT NULL,
|
||||||
|
`password` varchar(255) NOT NULL,
|
||||||
|
`name` varchar(255) DEFAULT NULL,
|
||||||
|
`surname` varchar(255) DEFAULT NULL,
|
||||||
|
`default_project` int(11) DEFAULT NULL,
|
||||||
|
`color` varchar(50) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `email` (`email`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_polish_ci;
|
||||||
|
|
||||||
|
-- Eksport danych został odznaczony.
|
||||||
|
|
||||||
|
/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */;
|
||||||
|
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
|
||||||
|
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;
|
||||||
|
|
||||||
|
-- ================================
|
||||||
|
-- DODANE: struktury kampanie > grupy/frazy
|
||||||
|
-- ================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `campaign_ad_groups` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`campaign_id` int(11) NOT NULL,
|
||||||
|
`ad_group_id` bigint(20) NOT NULL,
|
||||||
|
`ad_group_name` varchar(255) NOT NULL DEFAULT '',
|
||||||
|
`impressions_30` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`clicks_30` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`cost_30` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions_30` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversion_value_30` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`roas_30` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`impressions_all_time` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`clicks_all_time` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`cost_all_time` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions_all_time` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversion_value_all_time` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`roas_all_time` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`date_sync` date DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_campaign_ad_groups_campaign_ad_group` (`campaign_id`,`ad_group_id`),
|
||||||
|
KEY `idx_campaign_ad_groups_campaign_id` (`campaign_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `campaign_search_terms` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`campaign_id` int(11) NOT NULL,
|
||||||
|
`ad_group_id` int(11) NOT NULL,
|
||||||
|
`search_term` varchar(255) NOT NULL,
|
||||||
|
`impressions_30` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`clicks_30` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`cost_30` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions_30` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversion_value_30` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`roas_30` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`impressions_all_time` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`clicks_all_time` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`cost_all_time` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions_all_time` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversion_value_all_time` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`roas_all_time` decimal(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`date_sync` date DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_campaign_search_terms` (`campaign_id`,`ad_group_id`,`search_term`),
|
||||||
|
KEY `idx_campaign_search_terms_campaign_id` (`campaign_id`),
|
||||||
|
KEY `idx_campaign_search_terms_ad_group_id` (`ad_group_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `campaign_negative_keywords` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`campaign_id` int(11) NOT NULL,
|
||||||
|
`ad_group_id` int(11) DEFAULT NULL,
|
||||||
|
`scope` varchar(20) NOT NULL DEFAULT 'campaign',
|
||||||
|
`keyword_text` varchar(255) NOT NULL,
|
||||||
|
`match_type` varchar(40) DEFAULT NULL,
|
||||||
|
`date_sync` date DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_campaign_negative_keywords_campaign_id` (`campaign_id`),
|
||||||
|
KEY `idx_campaign_negative_keywords_ad_group_id` (`ad_group_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
195
install.php
Normal file
195
install.php
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
<?php
|
||||||
|
error_reporting( E_ALL );
|
||||||
|
date_default_timezone_set( 'Europe/Warsaw' );
|
||||||
|
|
||||||
|
if ( PHP_SAPI !== 'cli' )
|
||||||
|
{
|
||||||
|
header( 'Content-Type: text/plain; charset=utf-8' );
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/config.php';
|
||||||
|
|
||||||
|
function has_flag( $name )
|
||||||
|
{
|
||||||
|
if ( PHP_SAPI === 'cli' )
|
||||||
|
{
|
||||||
|
global $argv;
|
||||||
|
return in_array( '--' . $name, $argv, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
return isset( $_GET[ $name ] ) && (string) $_GET[ $name ] === '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
function statement_complete( $buffer, $delimiter )
|
||||||
|
{
|
||||||
|
$trimmed = rtrim( $buffer );
|
||||||
|
if ( $trimmed === '' )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dl = strlen( $delimiter );
|
||||||
|
if ( $dl === 0 )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return substr( $trimmed, -$dl ) === $delimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
function strip_statement_delimiter( $buffer, $delimiter )
|
||||||
|
{
|
||||||
|
$trimmed = rtrim( $buffer );
|
||||||
|
$dl = strlen( $delimiter );
|
||||||
|
|
||||||
|
if ( $dl > 0 && substr( $trimmed, -$dl ) === $delimiter )
|
||||||
|
{
|
||||||
|
return rtrim( substr( $trimmed, 0, -$dl ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function execute_sql_file( PDO $pdo, $file_path )
|
||||||
|
{
|
||||||
|
$handle = fopen( $file_path, 'r' );
|
||||||
|
if ( !$handle )
|
||||||
|
{
|
||||||
|
throw new RuntimeException( 'Nie mozna otworzyc pliku: ' . $file_path );
|
||||||
|
}
|
||||||
|
|
||||||
|
$delimiter = ';';
|
||||||
|
$buffer = '';
|
||||||
|
|
||||||
|
while ( ( $line = fgets( $handle ) ) !== false )
|
||||||
|
{
|
||||||
|
if ( preg_match( '/^\s*DELIMITER\s+(\S+)\s*$/i', $line, $m ) )
|
||||||
|
{
|
||||||
|
$delimiter = $m[1];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$trim = trim( $line );
|
||||||
|
if ( $trim !== '' && preg_match( '/^\s*(--|#)/', $trim ) )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer .= $line;
|
||||||
|
|
||||||
|
if ( statement_complete( $buffer, $delimiter ) )
|
||||||
|
{
|
||||||
|
$statement = strip_statement_delimiter( $buffer, $delimiter );
|
||||||
|
$statement = trim( $statement );
|
||||||
|
|
||||||
|
if ( $statement !== '' )
|
||||||
|
{
|
||||||
|
$pdo -> exec( $statement );
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose( $handle );
|
||||||
|
|
||||||
|
$tail = trim( $buffer );
|
||||||
|
if ( $tail !== '' )
|
||||||
|
{
|
||||||
|
$pdo -> exec( $tail );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$dsn = 'mysql:host=' . $database['host'] . ';dbname=' . $database['name'] . ';charset=utf8';
|
||||||
|
$pdo = new PDO( $dsn, $database['user'], $database['password'], [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
|
||||||
|
] );
|
||||||
|
|
||||||
|
$pdo -> exec(
|
||||||
|
'CREATE TABLE IF NOT EXISTS `schema_migrations` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`filename` VARCHAR(255) NOT NULL,
|
||||||
|
`applied_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_schema_migrations_filename` (`filename`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8'
|
||||||
|
);
|
||||||
|
|
||||||
|
$include_demo = has_flag( 'with_demo' );
|
||||||
|
$force = has_flag( 'force' );
|
||||||
|
|
||||||
|
$files = glob( __DIR__ . '/migrations/*.sql' );
|
||||||
|
sort( $files, SORT_NATURAL | SORT_FLAG_CASE );
|
||||||
|
|
||||||
|
$selected = [];
|
||||||
|
foreach ( $files as $file_path )
|
||||||
|
{
|
||||||
|
$filename = basename( $file_path );
|
||||||
|
|
||||||
|
if ( preg_match( '/^\d+_.+\.sql$/', $filename ) )
|
||||||
|
{
|
||||||
|
$selected[] = $file_path;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $include_demo && $filename === 'demo_data.sql' )
|
||||||
|
{
|
||||||
|
$selected[] = $file_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( empty( $selected ) )
|
||||||
|
{
|
||||||
|
echo "Brak migracji do uruchomienia.\n";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Uruchamianie migracji...\n";
|
||||||
|
echo 'Tryb force: ' . ( $force ? 'TAK' : 'NIE' ) . "\n";
|
||||||
|
echo 'Demo data: ' . ( $include_demo ? 'TAK' : 'NIE' ) . "\n\n";
|
||||||
|
|
||||||
|
$applied_count = 0;
|
||||||
|
$skipped_count = 0;
|
||||||
|
|
||||||
|
foreach ( $selected as $file_path )
|
||||||
|
{
|
||||||
|
$filename = basename( $file_path );
|
||||||
|
|
||||||
|
$stmt = $pdo -> prepare( 'SELECT COUNT(1) FROM schema_migrations WHERE filename = :filename' );
|
||||||
|
$stmt -> execute( [ ':filename' => $filename ] );
|
||||||
|
$already_applied = (int) $stmt -> fetchColumn() > 0;
|
||||||
|
|
||||||
|
if ( $already_applied && !$force )
|
||||||
|
{
|
||||||
|
echo '[SKIP] ' . $filename . " (juz zastosowana)\n";
|
||||||
|
$skipped_count++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute_sql_file( $pdo, $file_path );
|
||||||
|
|
||||||
|
$stmt = $pdo -> prepare(
|
||||||
|
'INSERT INTO schema_migrations (filename, applied_at)
|
||||||
|
VALUES (:filename, NOW())
|
||||||
|
ON DUPLICATE KEY UPDATE applied_at = NOW()'
|
||||||
|
);
|
||||||
|
$stmt -> execute( [ ':filename' => $filename ] );
|
||||||
|
|
||||||
|
echo '[OK] ' . $filename . "\n";
|
||||||
|
$applied_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\nZakonczono.\n";
|
||||||
|
echo 'Zastosowano: ' . $applied_count . "\n";
|
||||||
|
echo 'Pominieto: ' . $skipped_count . "\n";
|
||||||
|
}
|
||||||
|
catch ( Throwable $e )
|
||||||
|
{
|
||||||
|
http_response_code( 500 );
|
||||||
|
echo '[BLAD] ' . $e -> getMessage() . "\n";
|
||||||
|
exit( 1 );
|
||||||
|
}
|
||||||
@@ -1595,12 +1595,14 @@ table#products a.custom_name {
|
|||||||
color: #718096;
|
color: #718096;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .form-group .select2-container {
|
.jconfirm-box .form-group .select2-container,
|
||||||
|
.adspro-dialog-box .form-group .select2-container {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container--default .select2-selection--single {
|
.jconfirm-box .select2-container--default .select2-selection--single,
|
||||||
|
.adspro-dialog-box .select2-container--default .select2-selection--single {
|
||||||
background-color: #FFFFFF;
|
background-color: #FFFFFF;
|
||||||
border: 1px solid #E2E8F0;
|
border: 1px solid #E2E8F0;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -1613,42 +1615,50 @@ table#products a.custom_name {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__rendered {
|
.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__rendered,
|
||||||
|
.adspro-dialog-box .select2-container--default .select2-selection--single .select2-selection__rendered {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
color: #495057;
|
color: #495057;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__placeholder {
|
.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__placeholder,
|
||||||
|
.adspro-dialog-box .select2-container--default .select2-selection--single .select2-selection__placeholder {
|
||||||
color: #CBD5E0;
|
color: #CBD5E0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__arrow {
|
.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__arrow,
|
||||||
|
.adspro-dialog-box .select2-container--default .select2-selection--single .select2-selection__arrow {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container--default.select2-container--focus .select2-selection--single,
|
.jconfirm-box .select2-container--default.select2-container--focus .select2-selection--single,
|
||||||
.jconfirm-box .select2-container--default .select2-selection--single:hover {
|
.jconfirm-box .select2-container--default .select2-selection--single:hover,
|
||||||
|
.adspro-dialog-box .select2-container--default.select2-container--focus .select2-selection--single,
|
||||||
|
.adspro-dialog-box .select2-container--default .select2-selection--single:hover {
|
||||||
border-color: #6690F4;
|
border-color: #6690F4;
|
||||||
box-shadow: 0 0 0 3px rgba(102, 144, 244, 0.1);
|
box-shadow: 0 0 0 3px rgba(102, 144, 244, 0.1);
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container .select2-dropdown {
|
.jconfirm-box .select2-container .select2-dropdown,
|
||||||
|
.adspro-dialog-box .select2-container .select2-dropdown {
|
||||||
border-color: #E2E8F0;
|
border-color: #E2E8F0;
|
||||||
border-radius: 0 0 6px 6px;
|
border-radius: 0 0 6px 6px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container .select2-search--dropdown .select2-search__field {
|
.jconfirm-box .select2-container .select2-search--dropdown .select2-search__field,
|
||||||
|
.adspro-dialog-box .select2-container .select2-search--dropdown .select2-search__field {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #E2E8F0;
|
border: 1px solid #E2E8F0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container--default .select2-results__option--highlighted[aria-selected] {
|
.jconfirm-box .select2-container--default .select2-results__option--highlighted[aria-selected],
|
||||||
|
.adspro-dialog-box .select2-container--default .select2-results__option--highlighted[aria-selected] {
|
||||||
background-color: #6690F4;
|
background-color: #6690F4;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
}
|
}
|
||||||
@@ -1664,3 +1674,5 @@ table#products a.custom_name {
|
|||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*# sourceMappingURL=style.css.map */
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1906,12 +1906,14 @@ table#products {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Select2 w modalu ---
|
// --- Select2 w modalu ---
|
||||||
.jconfirm-box .form-group .select2-container {
|
.jconfirm-box .form-group .select2-container,
|
||||||
|
.adspro-dialog-box .form-group .select2-container {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container--default .select2-selection--single {
|
.jconfirm-box .select2-container--default .select2-selection--single,
|
||||||
|
.adspro-dialog-box .select2-container--default .select2-selection--single {
|
||||||
background-color: $cWhite;
|
background-color: $cWhite;
|
||||||
border: 1px solid $cBorder;
|
border: 1px solid $cBorder;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -1924,42 +1926,50 @@ table#products {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__rendered {
|
.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__rendered,
|
||||||
|
.adspro-dialog-box .select2-container--default .select2-selection--single .select2-selection__rendered {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
color: #495057;
|
color: #495057;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__placeholder {
|
.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__placeholder,
|
||||||
|
.adspro-dialog-box .select2-container--default .select2-selection--single .select2-selection__placeholder {
|
||||||
color: #CBD5E0;
|
color: #CBD5E0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__arrow {
|
.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__arrow,
|
||||||
|
.adspro-dialog-box .select2-container--default .select2-selection--single .select2-selection__arrow {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container--default.select2-container--focus .select2-selection--single,
|
.jconfirm-box .select2-container--default.select2-container--focus .select2-selection--single,
|
||||||
.jconfirm-box .select2-container--default .select2-selection--single:hover {
|
.jconfirm-box .select2-container--default .select2-selection--single:hover,
|
||||||
|
.adspro-dialog-box .select2-container--default.select2-container--focus .select2-selection--single,
|
||||||
|
.adspro-dialog-box .select2-container--default .select2-selection--single:hover {
|
||||||
border-color: $cPrimary;
|
border-color: $cPrimary;
|
||||||
box-shadow: 0 0 0 3px rgba($cPrimary, 0.1);
|
box-shadow: 0 0 0 3px rgba($cPrimary, 0.1);
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container .select2-dropdown {
|
.jconfirm-box .select2-container .select2-dropdown,
|
||||||
|
.adspro-dialog-box .select2-container .select2-dropdown {
|
||||||
border-color: $cBorder;
|
border-color: $cBorder;
|
||||||
border-radius: 0 0 6px 6px;
|
border-radius: 0 0 6px 6px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container .select2-search--dropdown .select2-search__field {
|
.jconfirm-box .select2-container .select2-search--dropdown .select2-search__field,
|
||||||
|
.adspro-dialog-box .select2-container .select2-search--dropdown .select2-search__field {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid $cBorder;
|
border: 1px solid $cBorder;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.jconfirm-box .select2-container--default .select2-results__option--highlighted[aria-selected] {
|
.jconfirm-box .select2-container--default .select2-results__option--highlighted[aria-selected],
|
||||||
|
.adspro-dialog-box .select2-container--default .select2-results__option--highlighted[aria-selected] {
|
||||||
background-color: $cPrimary;
|
background-color: $cPrimary;
|
||||||
color: $cWhite;
|
color: $cWhite;
|
||||||
}
|
}
|
||||||
|
|||||||
292
libraries/adspro-dialog.css
Normal file
292
libraries/adspro-dialog.css
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
/* =============================================================
|
||||||
|
AdsProDialog - Custom Dialog System
|
||||||
|
============================================================= */
|
||||||
|
|
||||||
|
/* --- Animacje --- */
|
||||||
|
@keyframes adspro-dialog-spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Główny wrapper --- */
|
||||||
|
.adspro-dialog {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.25s ease;
|
||||||
|
}
|
||||||
|
.adspro-dialog.adspro-dialog-open {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.adspro-dialog.adspro-dialog-closing {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Backdrop --- */
|
||||||
|
.adspro-dialog-bg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Scrollpane --- */
|
||||||
|
.adspro-dialog-scrollpane {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 40px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Center container --- */
|
||||||
|
.adspro-dialog-center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
margin: auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wsparcie columnClass (Bootstrap grid) */
|
||||||
|
.adspro-dialog-center.col-md-2 { max-width: 16.666%; }
|
||||||
|
.adspro-dialog-center.col-md-3 { max-width: 25%; }
|
||||||
|
.adspro-dialog-center.col-md-4 { max-width: 33.333%; }
|
||||||
|
.adspro-dialog-center.col-md-5 { max-width: 41.666%; }
|
||||||
|
.adspro-dialog-center.col-md-6 { max-width: 50%; }
|
||||||
|
.adspro-dialog-center.col-md-7 { max-width: 58.333%; }
|
||||||
|
.adspro-dialog-center.col-md-8 { max-width: 66.666%; }
|
||||||
|
.adspro-dialog-center.col-md-9 { max-width: 75%; }
|
||||||
|
.adspro-dialog-center.col-md-10 { max-width: 83.333%; }
|
||||||
|
.adspro-dialog-center.col-md-11 { max-width: 91.666%; }
|
||||||
|
.adspro-dialog-center.col-md-12 { max-width: 100%; }
|
||||||
|
.adspro-dialog-center.col-12 { max-width: 100%; }
|
||||||
|
|
||||||
|
/* --- Dialog box --- */
|
||||||
|
.adspro-dialog-box {
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2), 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 550px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transform: scale(0.95) translateY(-20px);
|
||||||
|
opacity: 0;
|
||||||
|
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
.adspro-dialog-open .adspro-dialog-box {
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.adspro-dialog-closing .adspro-dialog-box {
|
||||||
|
transform: scale(0.95) translateY(-10px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gdy użyto columnClass lub boxWidth, box zajmuje pełną szerokość kontenera */
|
||||||
|
.adspro-dialog-center[class*="col-"] .adspro-dialog-box {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.adspro-dialog-box[style*="max-width"] {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Kolorowe paski (type) --- */
|
||||||
|
.adspro-dialog-type-red { border-top: 4px solid #CC0000; }
|
||||||
|
.adspro-dialog-type-orange { border-top: 4px solid #FF8C00; }
|
||||||
|
.adspro-dialog-type-green { border-top: 4px solid #57B951; }
|
||||||
|
.adspro-dialog-type-blue { border-top: 4px solid #6690F4; }
|
||||||
|
.adspro-dialog-type-purple { border-top: 4px solid #8B5CF6; }
|
||||||
|
.adspro-dialog-type-dark { border-top: 4px solid #2D3748; }
|
||||||
|
|
||||||
|
/* --- Close icon --- */
|
||||||
|
.adspro-dialog-close-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 14px;
|
||||||
|
right: 14px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #A0AEC0;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
z-index: 10;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.adspro-dialog-close-icon:hover {
|
||||||
|
background: #FFF5F5;
|
||||||
|
color: #CC0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Tytuł --- */
|
||||||
|
.adspro-dialog-title-c {
|
||||||
|
padding: 20px 24px 0;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.adspro-dialog-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2D3748;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Kolor tytułu per typ */
|
||||||
|
.adspro-dialog-type-red .adspro-dialog-title { color: #CC0000; }
|
||||||
|
.adspro-dialog-type-orange .adspro-dialog-title { color: #FF8C00; }
|
||||||
|
.adspro-dialog-type-green .adspro-dialog-title { color: #2F855A; }
|
||||||
|
.adspro-dialog-type-blue .adspro-dialog-title { color: #6690F4; }
|
||||||
|
|
||||||
|
/* --- Content --- */
|
||||||
|
.adspro-dialog-content-pane {
|
||||||
|
padding: 16px 24px;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
.adspro-dialog-content {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #4E5E6A;
|
||||||
|
}
|
||||||
|
.adspro-dialog-content .form-group {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
.adspro-dialog-content .form-control {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Przyciski --- */
|
||||||
|
.adspro-dialog-buttons {
|
||||||
|
padding: 16px 24px 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
border-top: 1px solid #F1F5F9;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
.adspro-dialog-btn {
|
||||||
|
padding: 9px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-family: inherit;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.adspro-dialog-btn:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Klasy przycisków */
|
||||||
|
.adspro-dialog-btn.btn-blue {
|
||||||
|
background: #6690F4;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.adspro-dialog-btn.btn-blue:hover {
|
||||||
|
background: #3164db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adspro-dialog-btn.btn-red {
|
||||||
|
background: #CC0000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.adspro-dialog-btn.btn-red:hover {
|
||||||
|
background: #b30000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adspro-dialog-btn.btn-green {
|
||||||
|
background: #57B951;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.adspro-dialog-btn.btn-green:hover {
|
||||||
|
background: #4a9c3b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adspro-dialog-btn.btn-orange {
|
||||||
|
background: #FF8C00;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.adspro-dialog-btn.btn-orange:hover {
|
||||||
|
background: #e07800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adspro-dialog-btn.btn-success {
|
||||||
|
background: #57B951;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.adspro-dialog-btn.btn-success:hover {
|
||||||
|
background: #4a9c3b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adspro-dialog-btn.btn-danger {
|
||||||
|
background: #CC0000;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.adspro-dialog-btn.btn-danger:hover {
|
||||||
|
background: #b30000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adspro-dialog-btn.btn-default {
|
||||||
|
background: #E2E8F0;
|
||||||
|
color: #4E5E6A;
|
||||||
|
}
|
||||||
|
.adspro-dialog-btn.btn-default:hover {
|
||||||
|
background: #CBD5E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Loading overlay --- */
|
||||||
|
.adspro-dialog-loading {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.85);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 20;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.adspro-dialog-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid #E2E8F0;
|
||||||
|
border-top-color: #6690F4;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: adspro-dialog-spin 0.7s infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Responsywność --- */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.adspro-dialog-scrollpane {
|
||||||
|
padding: 20px 12px;
|
||||||
|
}
|
||||||
|
.adspro-dialog-box {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
.adspro-dialog-center[class*="col-"] {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
.adspro-dialog-buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.adspro-dialog-btn {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
351
libraries/adspro-dialog.js
Normal file
351
libraries/adspro-dialog.js
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
/**
|
||||||
|
* AdsProDialog - własny system dialogów (zamiennik jquery-confirm)
|
||||||
|
* API kompatybilne z jquery-confirm: $.confirm(options), $.alert(options)
|
||||||
|
*/
|
||||||
|
(function( $ )
|
||||||
|
{
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var dialogCounter = 0;
|
||||||
|
var activeDialogs = [];
|
||||||
|
|
||||||
|
function AdsProDialog( options )
|
||||||
|
{
|
||||||
|
this.id = ++dialogCounter;
|
||||||
|
this.options = $.extend( {}, AdsProDialog.defaults, options );
|
||||||
|
this.$el = null;
|
||||||
|
this.$box = null;
|
||||||
|
this.$content = null;
|
||||||
|
this._closed = false;
|
||||||
|
this._autoCloseTimer = null;
|
||||||
|
this._init();
|
||||||
|
}
|
||||||
|
|
||||||
|
AdsProDialog.defaults = {
|
||||||
|
title: '',
|
||||||
|
content: '',
|
||||||
|
type: '',
|
||||||
|
theme: '',
|
||||||
|
buttons: {},
|
||||||
|
closeIcon: false,
|
||||||
|
closeIconClass: 'fa-solid fa-xmark',
|
||||||
|
columnClass: '',
|
||||||
|
boxWidth: '',
|
||||||
|
useBootstrap: true,
|
||||||
|
draggable: false,
|
||||||
|
autoClose: '',
|
||||||
|
onContentReady: null,
|
||||||
|
onClose: null,
|
||||||
|
onOpen: null
|
||||||
|
};
|
||||||
|
|
||||||
|
AdsProDialog.prototype = {
|
||||||
|
|
||||||
|
_init: function()
|
||||||
|
{
|
||||||
|
this._buildDOM();
|
||||||
|
this._bindEvents();
|
||||||
|
this._appendToBody();
|
||||||
|
this._applyAutoClose();
|
||||||
|
this._triggerContentReady();
|
||||||
|
activeDialogs.push( this );
|
||||||
|
},
|
||||||
|
|
||||||
|
_buildDOM: function()
|
||||||
|
{
|
||||||
|
var o = this.options;
|
||||||
|
var typeClass = o.type ? ' adspro-dialog-type-' + o.type : '';
|
||||||
|
|
||||||
|
var sizeStyle = '';
|
||||||
|
var sizeClass = '';
|
||||||
|
if ( !o.useBootstrap && o.boxWidth )
|
||||||
|
{
|
||||||
|
sizeStyle = 'max-width:' + o.boxWidth + ';width:100%;';
|
||||||
|
}
|
||||||
|
else if ( o.columnClass )
|
||||||
|
{
|
||||||
|
sizeClass = ' ' + o.columnClass.replace( /col-md-offset-\d+/g, '' ).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
var html =
|
||||||
|
'<div class="adspro-dialog" data-dialog-id="' + this.id + '">' +
|
||||||
|
'<div class="adspro-dialog-bg"></div>' +
|
||||||
|
'<div class="adspro-dialog-scrollpane">' +
|
||||||
|
'<div class="adspro-dialog-center' + sizeClass + '">' +
|
||||||
|
'<div class="adspro-dialog-box jconfirm-box' + typeClass + '" style="' + sizeStyle + '">' +
|
||||||
|
this._buildCloseIcon( o ) +
|
||||||
|
this._buildHeader( o ) +
|
||||||
|
'<div class="adspro-dialog-content-pane">' +
|
||||||
|
'<div class="adspro-dialog-content">' + o.content + '</div>' +
|
||||||
|
'</div>' +
|
||||||
|
this._buildButtons( o ) +
|
||||||
|
'<div class="adspro-dialog-loading" style="display:none;">' +
|
||||||
|
'<div class="adspro-dialog-spinner"></div>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
this.$el = $( html );
|
||||||
|
this.$box = this.$el.find( '.adspro-dialog-box' );
|
||||||
|
this.$content = this.$el.find( '.adspro-dialog-content' );
|
||||||
|
},
|
||||||
|
|
||||||
|
_buildCloseIcon: function( o )
|
||||||
|
{
|
||||||
|
if ( !o.closeIcon ) return '';
|
||||||
|
var iconClass = o.closeIconClass || 'fa-solid fa-xmark';
|
||||||
|
return '<div class="adspro-dialog-close-icon"><i class="' + iconClass + '"></i></div>';
|
||||||
|
},
|
||||||
|
|
||||||
|
_buildHeader: function( o )
|
||||||
|
{
|
||||||
|
if ( !o.title ) return '';
|
||||||
|
return '<div class="adspro-dialog-title-c">' +
|
||||||
|
'<span class="adspro-dialog-title">' + o.title + '</span>' +
|
||||||
|
'</div>';
|
||||||
|
},
|
||||||
|
|
||||||
|
_buildButtons: function( o )
|
||||||
|
{
|
||||||
|
if ( !o.buttons || $.isEmptyObject( o.buttons ) ) return '';
|
||||||
|
|
||||||
|
var html = '<div class="adspro-dialog-buttons">';
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
$.each( o.buttons, function( key, btnDef )
|
||||||
|
{
|
||||||
|
if ( typeof btnDef === 'function' )
|
||||||
|
{
|
||||||
|
btnDef = { action: btnDef };
|
||||||
|
o.buttons[ key ] = btnDef;
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = btnDef.text || key.charAt( 0 ).toUpperCase() + key.slice( 1 );
|
||||||
|
var btnClass = btnDef.btnClass || 'btn-default';
|
||||||
|
var isEnter = ( btnDef.keys && btnDef.keys.indexOf( 'enter' ) !== -1 );
|
||||||
|
|
||||||
|
html += '<button type="button" class="adspro-dialog-btn btn ' + btnClass + '"' +
|
||||||
|
' data-btn-key="' + key + '"' +
|
||||||
|
( isEnter ? ' data-enter-key="true"' : '' ) +
|
||||||
|
'>' + text + '</button>';
|
||||||
|
});
|
||||||
|
|
||||||
|
html += '</div>';
|
||||||
|
return html;
|
||||||
|
},
|
||||||
|
|
||||||
|
_bindEvents: function()
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
var o = this.options;
|
||||||
|
|
||||||
|
// Backdrop click
|
||||||
|
this.$el.find( '.adspro-dialog-bg' ).on( 'click', function()
|
||||||
|
{
|
||||||
|
self.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close icon
|
||||||
|
this.$el.find( '.adspro-dialog-close-icon' ).on( 'click', function()
|
||||||
|
{
|
||||||
|
self.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
this.$el.find( '.adspro-dialog-btn' ).each( function()
|
||||||
|
{
|
||||||
|
var $btn = $( this );
|
||||||
|
var key = $btn.data( 'btn-key' );
|
||||||
|
var btnDef = o.buttons[ key ];
|
||||||
|
|
||||||
|
// Referencje do buttonów (kompatybilność z $$formSubmit itp.)
|
||||||
|
self[ '$$' + key ] = $btn;
|
||||||
|
|
||||||
|
$btn.on( 'click', function()
|
||||||
|
{
|
||||||
|
if ( typeof btnDef === 'function' )
|
||||||
|
{
|
||||||
|
btnDef.call( self );
|
||||||
|
self.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( btnDef && typeof btnDef.action === 'function' )
|
||||||
|
{
|
||||||
|
var result = btnDef.action.call( self );
|
||||||
|
if ( result !== false )
|
||||||
|
{
|
||||||
|
self.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Keyboard
|
||||||
|
$( document ).on( 'keydown.adspro-dialog-' + this.id, function( e )
|
||||||
|
{
|
||||||
|
if ( self._closed ) return;
|
||||||
|
if ( activeDialogs[ activeDialogs.length - 1 ] !== self ) return;
|
||||||
|
|
||||||
|
if ( e.key === 'Escape' )
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
self.close();
|
||||||
|
}
|
||||||
|
if ( e.key === 'Enter' )
|
||||||
|
{
|
||||||
|
if ( $( e.target ).is( 'textarea, select' ) ) return;
|
||||||
|
var $enterBtn = self.$el.find( '.adspro-dialog-btn[data-enter-key="true"]' );
|
||||||
|
if ( $enterBtn.length )
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
$enterBtn.trigger( 'click' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draggable
|
||||||
|
if ( o.draggable && $.fn.draggable )
|
||||||
|
{
|
||||||
|
this.$box.draggable({
|
||||||
|
handle: '.adspro-dialog-title-c',
|
||||||
|
cursor: 'move'
|
||||||
|
});
|
||||||
|
this.$el.find( '.adspro-dialog-title-c' ).css( 'cursor', 'move' );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_appendToBody: function()
|
||||||
|
{
|
||||||
|
var baseZIndex = 99000 + ( this.id * 10 );
|
||||||
|
this.$el.css( 'z-index', baseZIndex );
|
||||||
|
$( 'body' ).append( this.$el );
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
requestAnimationFrame( function()
|
||||||
|
{
|
||||||
|
self.$el.addClass( 'adspro-dialog-open' );
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( typeof this.options.onOpen === 'function' )
|
||||||
|
{
|
||||||
|
this.options.onOpen.call( this );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_applyAutoClose: function()
|
||||||
|
{
|
||||||
|
var ac = this.options.autoClose;
|
||||||
|
if ( !ac ) return;
|
||||||
|
|
||||||
|
var parts = ac.split( '|' );
|
||||||
|
if ( parts.length !== 2 ) return;
|
||||||
|
|
||||||
|
var ms = parseInt( parts[ 1 ], 10 );
|
||||||
|
var self = this;
|
||||||
|
this._autoCloseTimer = setTimeout( function()
|
||||||
|
{
|
||||||
|
if ( !self._closed ) self.close();
|
||||||
|
}, ms );
|
||||||
|
},
|
||||||
|
|
||||||
|
_triggerContentReady: function()
|
||||||
|
{
|
||||||
|
if ( typeof this.options.onContentReady === 'function' )
|
||||||
|
{
|
||||||
|
this.options.onContentReady.call( this );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// --- Metody publiczne ---
|
||||||
|
|
||||||
|
close: function()
|
||||||
|
{
|
||||||
|
if ( this._closed ) return;
|
||||||
|
this._closed = true;
|
||||||
|
|
||||||
|
if ( this._autoCloseTimer ) clearTimeout( this._autoCloseTimer );
|
||||||
|
$( document ).off( 'keydown.adspro-dialog-' + this.id );
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.$el.removeClass( 'adspro-dialog-open' );
|
||||||
|
this.$el.addClass( 'adspro-dialog-closing' );
|
||||||
|
|
||||||
|
setTimeout( function()
|
||||||
|
{
|
||||||
|
self.$el.remove();
|
||||||
|
var idx = activeDialogs.indexOf( self );
|
||||||
|
if ( idx > -1 ) activeDialogs.splice( idx, 1 );
|
||||||
|
if ( typeof self.options.onClose === 'function' )
|
||||||
|
{
|
||||||
|
self.options.onClose.call( self );
|
||||||
|
}
|
||||||
|
}, 250 );
|
||||||
|
},
|
||||||
|
|
||||||
|
showLoading: function( showContent )
|
||||||
|
{
|
||||||
|
this.$el.find( '.adspro-dialog-loading' ).show();
|
||||||
|
if ( !showContent )
|
||||||
|
{
|
||||||
|
this.$el.find( '.adspro-dialog-content-pane' ).css( 'opacity', '0.3' );
|
||||||
|
}
|
||||||
|
this.$el.find( '.adspro-dialog-buttons' ).css({
|
||||||
|
'pointer-events': 'none',
|
||||||
|
'opacity': '0.5'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
hideLoading: function()
|
||||||
|
{
|
||||||
|
this.$el.find( '.adspro-dialog-loading' ).hide();
|
||||||
|
this.$el.find( '.adspro-dialog-content-pane' ).css( 'opacity', '' );
|
||||||
|
this.$el.find( '.adspro-dialog-buttons' ).css({
|
||||||
|
'pointer-events': '',
|
||||||
|
'opacity': ''
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setContent: function( html )
|
||||||
|
{
|
||||||
|
this.$content.html( html );
|
||||||
|
},
|
||||||
|
|
||||||
|
setTitle: function( title )
|
||||||
|
{
|
||||||
|
this.$el.find( '.adspro-dialog-title' ).html( title );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Rejestracja globalna ---
|
||||||
|
|
||||||
|
$.confirm = function( options )
|
||||||
|
{
|
||||||
|
return new AdsProDialog( options );
|
||||||
|
};
|
||||||
|
|
||||||
|
$.alert = function( options )
|
||||||
|
{
|
||||||
|
if ( typeof options === 'string' )
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
title: '',
|
||||||
|
content: options,
|
||||||
|
buttons: { ok: { text: 'OK', btnClass: 'btn-blue' } }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !options.buttons )
|
||||||
|
{
|
||||||
|
options.buttons = { ok: { text: 'OK', btnClass: 'btn-blue' } };
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AdsProDialog( options );
|
||||||
|
};
|
||||||
|
|
||||||
|
})( jQuery );
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
-- Migracja: Tabela settings + kolumna google_ads_customer_id
|
-- Migracja: Tabela settings + kolumny Google Ads w clients
|
||||||
-- Data: 2026-02-15
|
-- Data: 2026-02-15
|
||||||
-- Opis: Dodaje globalną tabelę ustawień key-value oraz kolumnę Google Ads Customer ID do tabeli clients
|
-- Opis: Idempotentna migracja (bez bledow i bez duplikatow przy ponownym uruchomieniu)
|
||||||
|
|
||||||
-- 1. Tabela settings (globalne ustawienia aplikacji)
|
-- 1. Tabela settings (globalne ustawienia aplikacji)
|
||||||
CREATE TABLE IF NOT EXISTS `settings` (
|
CREATE TABLE IF NOT EXISTS `settings` (
|
||||||
@@ -11,8 +11,34 @@ CREATE TABLE IF NOT EXISTS `settings` (
|
|||||||
UNIQUE KEY `uk_setting_key` (`setting_key`)
|
UNIQUE KEY `uk_setting_key` (`setting_key`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||||
|
|
||||||
-- 2. Kolumna google_ads_customer_id w tabeli clients
|
-- 2. Kolumna google_ads_customer_id w tabeli clients (tylko jesli nie istnieje)
|
||||||
ALTER TABLE `clients` ADD COLUMN `google_ads_customer_id` VARCHAR(20) NULL DEFAULT NULL AFTER `name`;
|
SET @sql = IF(
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = 'clients'
|
||||||
|
AND COLUMN_NAME = 'google_ads_customer_id'
|
||||||
|
),
|
||||||
|
'DO 1',
|
||||||
|
'ALTER TABLE `clients` ADD COLUMN `google_ads_customer_id` VARCHAR(20) NULL DEFAULT NULL AFTER `name`'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|
||||||
-- 3. Kolumna google_ads_start_date w tabeli clients (data od kiedy pobierać dane z Google Ads API)
|
-- 3. Kolumna google_ads_start_date w tabeli clients (tylko jesli nie istnieje)
|
||||||
ALTER TABLE `clients` ADD COLUMN `google_ads_start_date` DATE NULL DEFAULT NULL AFTER `google_ads_customer_id`;
|
SET @sql = IF(
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = 'clients'
|
||||||
|
AND COLUMN_NAME = 'google_ads_start_date'
|
||||||
|
),
|
||||||
|
'DO 1',
|
||||||
|
'ALTER TABLE `clients` ADD COLUMN `google_ads_start_date` DATE NULL DEFAULT NULL AFTER `google_ads_customer_id`'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|||||||
@@ -1 +1,17 @@
|
|||||||
ALTER TABLE `products_data` ADD COLUMN `product_url` VARCHAR(500) NULL DEFAULT NULL;
|
-- Migracja: products_data.product_url
|
||||||
|
-- Opis: Idempotentna migracja (bez bledu przy ponownym uruchomieniu)
|
||||||
|
|
||||||
|
SET @sql = IF(
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
|
AND TABLE_NAME = 'products_data'
|
||||||
|
AND COLUMN_NAME = 'product_url'
|
||||||
|
),
|
||||||
|
'DO 1',
|
||||||
|
'ALTER TABLE `products_data` ADD COLUMN `product_url` VARCHAR(500) NULL DEFAULT NULL'
|
||||||
|
);
|
||||||
|
PREPARE stmt FROM @sql;
|
||||||
|
EXECUTE stmt;
|
||||||
|
DEALLOCATE PREPARE stmt;
|
||||||
|
|||||||
77
migrations/003_campaign_ad_groups_and_terms.sql
Normal file
77
migrations/003_campaign_ad_groups_and_terms.sql
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
-- Migracja: grupy reklam + frazy wyszukiwane i wykluczajace dla kampanii
|
||||||
|
-- Opis: struktura pod import z Google Ads API (30 dni + all time)
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `campaign_ad_groups` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`campaign_id` INT(11) NOT NULL,
|
||||||
|
`ad_group_id` BIGINT(20) NOT NULL,
|
||||||
|
`ad_group_name` VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
`impressions_30` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`clicks_30` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`cost_30` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions_30` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversion_value_30` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`roas_30` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`impressions_all_time` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`clicks_all_time` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`cost_all_time` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions_all_time` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversion_value_all_time` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`roas_all_time` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`date_sync` DATE DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_campaign_ad_groups_campaign_ad_group` (`campaign_id`, `ad_group_id`),
|
||||||
|
KEY `idx_campaign_ad_groups_campaign_id` (`campaign_id`),
|
||||||
|
CONSTRAINT `FK_campaign_ad_groups_campaigns`
|
||||||
|
FOREIGN KEY (`campaign_id`) REFERENCES `campaigns` (`id`)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `campaign_search_terms` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`campaign_id` INT(11) NOT NULL,
|
||||||
|
`ad_group_id` INT(11) NOT NULL,
|
||||||
|
`search_term` VARCHAR(255) NOT NULL,
|
||||||
|
`impressions_30` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`clicks_30` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`cost_30` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions_30` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversion_value_30` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`roas_30` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`impressions_all_time` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`clicks_all_time` INT(11) NOT NULL DEFAULT 0,
|
||||||
|
`cost_all_time` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversions_all_time` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`conversion_value_all_time` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`roas_all_time` DECIMAL(20,6) NOT NULL DEFAULT 0.000000,
|
||||||
|
`date_sync` DATE DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `uk_campaign_search_terms` (`campaign_id`, `ad_group_id`, `search_term`),
|
||||||
|
KEY `idx_campaign_search_terms_campaign_id` (`campaign_id`),
|
||||||
|
KEY `idx_campaign_search_terms_ad_group_id` (`ad_group_id`),
|
||||||
|
CONSTRAINT `FK_campaign_search_terms_campaigns`
|
||||||
|
FOREIGN KEY (`campaign_id`) REFERENCES `campaigns` (`id`)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `FK_campaign_search_terms_ad_groups`
|
||||||
|
FOREIGN KEY (`ad_group_id`) REFERENCES `campaign_ad_groups` (`id`)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `campaign_negative_keywords` (
|
||||||
|
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`campaign_id` INT(11) NOT NULL,
|
||||||
|
`ad_group_id` INT(11) DEFAULT NULL,
|
||||||
|
`scope` VARCHAR(20) NOT NULL DEFAULT 'campaign',
|
||||||
|
`keyword_text` VARCHAR(255) NOT NULL,
|
||||||
|
`match_type` VARCHAR(40) DEFAULT NULL,
|
||||||
|
`date_sync` DATE DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_campaign_negative_keywords_campaign_id` (`campaign_id`),
|
||||||
|
KEY `idx_campaign_negative_keywords_ad_group_id` (`ad_group_id`),
|
||||||
|
CONSTRAINT `FK_campaign_negative_keywords_campaigns`
|
||||||
|
FOREIGN KEY (`campaign_id`) REFERENCES `campaigns` (`id`)
|
||||||
|
ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `FK_campaign_negative_keywords_ad_groups`
|
||||||
|
FOREIGN KEY (`ad_group_id`) REFERENCES `campaign_ad_groups` (`id`)
|
||||||
|
ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
@@ -11,6 +11,23 @@
|
|||||||
-- 1. KAMPANIE
|
-- 1. KAMPANIE
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Idempotencja: usuń poprzedni zestaw danych demo kampanii przed ponownym wstawieniem
|
||||||
|
DELETE ch
|
||||||
|
FROM campaigns_history ch
|
||||||
|
JOIN campaigns c ON c.id = ch.campaign_id
|
||||||
|
WHERE c.client_id = 2
|
||||||
|
AND c.campaign_id IN (
|
||||||
|
20845671001, 20845671002, 20845671003, 20845671004,
|
||||||
|
20845671005, 20845671006, 20845671007, 20845671008
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM campaigns
|
||||||
|
WHERE client_id = 2
|
||||||
|
AND campaign_id IN (
|
||||||
|
20845671001, 20845671002, 20845671003, 20845671004,
|
||||||
|
20845671005, 20845671006, 20845671007, 20845671008
|
||||||
|
);
|
||||||
|
|
||||||
INSERT INTO `campaigns` (`client_id`, `campaign_id`, `campaign_name`) VALUES
|
INSERT INTO `campaigns` (`client_id`, `campaign_id`, `campaign_name`) VALUES
|
||||||
(2, 20845671001, 'PMAX | Prezenty personalizowane'),
|
(2, 20845671001, 'PMAX | Prezenty personalizowane'),
|
||||||
(2, 20845671002, 'PMAX | Bestsellery'),
|
(2, 20845671002, 'PMAX | Bestsellery'),
|
||||||
@@ -152,6 +169,53 @@ DROP PROCEDURE IF EXISTS generate_campaign_history;
|
|||||||
-- 3. PRODUKTY (25 produktów - realistyczne nazwy sklepu z prezentami)
|
-- 3. PRODUKTY (25 produktów - realistyczne nazwy sklepu z prezentami)
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Idempotencja: usuń poprzedni zestaw danych demo produktów przed ponownym wstawieniem
|
||||||
|
DELETE pt
|
||||||
|
FROM products_temp pt
|
||||||
|
JOIN products p ON p.id = pt.product_id
|
||||||
|
WHERE p.client_id = 2
|
||||||
|
AND p.offer_id IN (
|
||||||
|
'shopify_PL_8901001','shopify_PL_8901002','shopify_PL_8901003','shopify_PL_8901004','shopify_PL_8901005',
|
||||||
|
'shopify_PL_8901006','shopify_PL_8901007','shopify_PL_8901008','shopify_PL_8901009','shopify_PL_8901010',
|
||||||
|
'shopify_PL_8901011','shopify_PL_8901012','shopify_PL_8901013','shopify_PL_8901014','shopify_PL_8901015',
|
||||||
|
'shopify_PL_8901016','shopify_PL_8901017','shopify_PL_8901018','shopify_PL_8901019','shopify_PL_8901020',
|
||||||
|
'shopify_PL_8901021','shopify_PL_8901022','shopify_PL_8901023','shopify_PL_8901024','shopify_PL_8901025'
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE pd
|
||||||
|
FROM products_data pd
|
||||||
|
JOIN products p ON p.id = pd.product_id
|
||||||
|
WHERE p.client_id = 2
|
||||||
|
AND p.offer_id IN (
|
||||||
|
'shopify_PL_8901001','shopify_PL_8901002','shopify_PL_8901003','shopify_PL_8901004','shopify_PL_8901005',
|
||||||
|
'shopify_PL_8901006','shopify_PL_8901007','shopify_PL_8901008','shopify_PL_8901009','shopify_PL_8901010',
|
||||||
|
'shopify_PL_8901011','shopify_PL_8901012','shopify_PL_8901013','shopify_PL_8901014','shopify_PL_8901015',
|
||||||
|
'shopify_PL_8901016','shopify_PL_8901017','shopify_PL_8901018','shopify_PL_8901019','shopify_PL_8901020',
|
||||||
|
'shopify_PL_8901021','shopify_PL_8901022','shopify_PL_8901023','shopify_PL_8901024','shopify_PL_8901025'
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE ph
|
||||||
|
FROM products_history ph
|
||||||
|
JOIN products p ON p.id = ph.product_id
|
||||||
|
WHERE p.client_id = 2
|
||||||
|
AND p.offer_id IN (
|
||||||
|
'shopify_PL_8901001','shopify_PL_8901002','shopify_PL_8901003','shopify_PL_8901004','shopify_PL_8901005',
|
||||||
|
'shopify_PL_8901006','shopify_PL_8901007','shopify_PL_8901008','shopify_PL_8901009','shopify_PL_8901010',
|
||||||
|
'shopify_PL_8901011','shopify_PL_8901012','shopify_PL_8901013','shopify_PL_8901014','shopify_PL_8901015',
|
||||||
|
'shopify_PL_8901016','shopify_PL_8901017','shopify_PL_8901018','shopify_PL_8901019','shopify_PL_8901020',
|
||||||
|
'shopify_PL_8901021','shopify_PL_8901022','shopify_PL_8901023','shopify_PL_8901024','shopify_PL_8901025'
|
||||||
|
);
|
||||||
|
|
||||||
|
DELETE FROM products
|
||||||
|
WHERE client_id = 2
|
||||||
|
AND offer_id IN (
|
||||||
|
'shopify_PL_8901001','shopify_PL_8901002','shopify_PL_8901003','shopify_PL_8901004','shopify_PL_8901005',
|
||||||
|
'shopify_PL_8901006','shopify_PL_8901007','shopify_PL_8901008','shopify_PL_8901009','shopify_PL_8901010',
|
||||||
|
'shopify_PL_8901011','shopify_PL_8901012','shopify_PL_8901013','shopify_PL_8901014','shopify_PL_8901015',
|
||||||
|
'shopify_PL_8901016','shopify_PL_8901017','shopify_PL_8901018','shopify_PL_8901019','shopify_PL_8901020',
|
||||||
|
'shopify_PL_8901021','shopify_PL_8901022','shopify_PL_8901023','shopify_PL_8901024','shopify_PL_8901025'
|
||||||
|
);
|
||||||
|
|
||||||
INSERT INTO `products` (`client_id`, `offer_id`, `name`) VALUES
|
INSERT INTO `products` (`client_id`, `offer_id`, `name`) VALUES
|
||||||
(2, 'shopify_PL_8901001', 'Kubek personalizowany ze zdjęciem - Biały 330ml'),
|
(2, 'shopify_PL_8901001', 'Kubek personalizowany ze zdjęciem - Biały 330ml'),
|
||||||
(2, 'shopify_PL_8901002', 'Poduszka z własnym nadrukiem 40x40cm'),
|
(2, 'shopify_PL_8901002', 'Poduszka z własnym nadrukiem 40x40cm'),
|
||||||
@@ -296,6 +360,12 @@ DROP PROCEDURE IF EXISTS generate_product_history;
|
|||||||
-- 5. PRODUCTS_TEMP (zagregowane dane - jak po cron_products)
|
-- 5. PRODUCTS_TEMP (zagregowane dane - jak po cron_products)
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Idempotencja: wyczyść bieżące agregaty klienta przed ponownym przeliczeniem
|
||||||
|
DELETE pt
|
||||||
|
FROM products_temp pt
|
||||||
|
JOIN products p ON p.id = pt.product_id
|
||||||
|
WHERE p.client_id = 2;
|
||||||
|
|
||||||
INSERT INTO products_temp (product_id, name, impressions, impressions_30, clicks, clicks_30, ctr, cost, conversions, conversions_value, cpc, roas)
|
INSERT INTO products_temp (product_id, name, impressions, impressions_30, clicks, clicks_30, ctr, cost, conversions, conversions_value, cpc, roas)
|
||||||
SELECT
|
SELECT
|
||||||
p.id,
|
p.id,
|
||||||
|
|||||||
762
templates/campaign_terms/main_view.php
Normal file
762
templates/campaign_terms/main_view.php
Normal file
@@ -0,0 +1,762 @@
|
|||||||
|
<div class="campaigns-page campaign-terms-page">
|
||||||
|
<div class="campaigns-header">
|
||||||
|
<h2><i class="fa-solid fa-list-check"></i> Grupy i frazy</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="campaigns-filters">
|
||||||
|
<div class="filter-group">
|
||||||
|
<label for="terms_client_id"><i class="fa-solid fa-building"></i> Klient</label>
|
||||||
|
<select id="terms_client_id" name="terms_client_id" class="form-control">
|
||||||
|
<option value="">- wybierz klienta -</option>
|
||||||
|
<?php foreach ( $this -> clients as $client ): ?>
|
||||||
|
<option value="<?= $client['id']; ?>"><?= htmlspecialchars( $client['name'] ); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="filter-group">
|
||||||
|
<label for="terms_campaign_id"><i class="fa-solid fa-bullhorn"></i> Kampania</label>
|
||||||
|
<select id="terms_campaign_id" name="terms_campaign_id" class="form-control">
|
||||||
|
<option value="">- wybierz kampanie -</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="filter-group">
|
||||||
|
<label for="terms_ad_group_id"><i class="fa-solid fa-layer-group"></i> Grupa reklam</label>
|
||||||
|
<select id="terms_ad_group_id" name="terms_ad_group_id" class="form-control">
|
||||||
|
<option value="">- wszystkie grupy -</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="filter-group">
|
||||||
|
<label for="phrase_search"><i class="fa-solid fa-magnifying-glass"></i> Szukaj frazy</label>
|
||||||
|
<input type="text" id="phrase_search" class="form-control" placeholder="Wpisz fragment frazy..." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="campaign-terms-wrap">
|
||||||
|
<div class="campaigns-extra-card terms-card terms-adgroups-card">
|
||||||
|
<div class="campaigns-extra-card-title">
|
||||||
|
<i class="fa-solid fa-layer-group"></i> Grupy reklam
|
||||||
|
</div>
|
||||||
|
<div class="campaigns-extra-table-wrap">
|
||||||
|
<table class="table table-sm campaigns-extra-table" id="terms_ad_groups_table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Grupa reklam</th>
|
||||||
|
<th>Klik. 30d</th>
|
||||||
|
<th>Koszt 30d</th>
|
||||||
|
<th>Wartosc 30d</th>
|
||||||
|
<th>ROAS 30d</th>
|
||||||
|
<th>Klik. all</th>
|
||||||
|
<th>Koszt all</th>
|
||||||
|
<th>Wartosc all</th>
|
||||||
|
<th>ROAS all</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td colspan="9" class="campaigns-empty-row">Wybierz klienta i kampanie.</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="campaigns-extra-card terms-card terms-search-card">
|
||||||
|
<div class="campaigns-extra-card-title">
|
||||||
|
<i class="fa-solid fa-magnifying-glass"></i> Frazy wyszukiwane (klikniecia >= 1)
|
||||||
|
</div>
|
||||||
|
<div class="campaigns-extra-table-wrap">
|
||||||
|
<table class="table table-sm campaigns-extra-table" id="terms_search_table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Fraza</th>
|
||||||
|
<th>Grupa reklam</th>
|
||||||
|
<th>Klik. 30d</th>
|
||||||
|
<th>Koszt 30d</th>
|
||||||
|
<th>Wartosc 30d</th>
|
||||||
|
<th>ROAS 30d</th>
|
||||||
|
<th>Klik. all</th>
|
||||||
|
<th>Koszt all</th>
|
||||||
|
<th>Wartosc all</th>
|
||||||
|
<th>ROAS all</th>
|
||||||
|
<th>Akcja</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td colspan="11" class="campaigns-empty-row">Brak danych.</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="campaigns-extra-card terms-card terms-negative-card">
|
||||||
|
<div class="campaigns-extra-card-title">
|
||||||
|
<i class="fa-solid fa-ban"></i> Frazy wykluczajace
|
||||||
|
</div>
|
||||||
|
<div class="campaigns-extra-table-wrap">
|
||||||
|
<table class="table table-sm campaigns-extra-table" id="terms_negative_table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Poziom</th>
|
||||||
|
<th>Fraza</th>
|
||||||
|
<th>Match type</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td colspan="3" class="campaigns-empty-row">Brak danych.</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.campaign-terms-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.campaign-terms-page .campaigns-filters {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.campaign-terms-page .campaigns-filters .filter-group {
|
||||||
|
min-width: 220px;
|
||||||
|
}
|
||||||
|
.campaign-terms-page {
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.campaigns-extra-card {
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.campaigns-extra-card-title {
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid #E2E8F0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #334155;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.campaigns-extra-table-wrap {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.campaigns-extra-table {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
.campaigns-extra-table thead th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
|
background: #F8FAFC;
|
||||||
|
border-bottom: 1px solid #E2E8F0;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .4px;
|
||||||
|
color: #64748B;
|
||||||
|
padding: 10px 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.campaigns-extra-table tbody td {
|
||||||
|
padding: 9px 12px;
|
||||||
|
border-bottom: 1px solid #F1F5F9;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #334155;
|
||||||
|
vertical-align: middle;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.campaigns-extra-table td.num-cell {
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.campaigns-extra-table td.text-cell {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.campaigns-extra-table th.phrase-nowrap,
|
||||||
|
.campaigns-extra-table td.phrase-nowrap {
|
||||||
|
white-space: nowrap !important;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.campaigns-extra-table .terms-add-negative-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #E2E8F0;
|
||||||
|
background: #EEF2FF;
|
||||||
|
color: #3B82F6;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
.campaigns-extra-table .terms-add-negative-btn:hover {
|
||||||
|
background: #3B82F6;
|
||||||
|
color: #FFFFFF;
|
||||||
|
border-color: #3B82F6;
|
||||||
|
}
|
||||||
|
.campaigns-extra-table tbody tr:hover {
|
||||||
|
background: #F8FAFC;
|
||||||
|
}
|
||||||
|
.campaigns-extra-table tbody tr.term-is-negative td {
|
||||||
|
color: #DC2626;
|
||||||
|
}
|
||||||
|
.campaigns-extra-table tbody tr.term-is-negative:hover {
|
||||||
|
background: #FEF2F2;
|
||||||
|
}
|
||||||
|
.campaigns-empty-row {
|
||||||
|
text-align: center;
|
||||||
|
color: #94A3B8 !important;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.campaign-terms-page .dt-layout-row:first-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.campaign-terms-page .dt-layout-row {
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin: 0 !important;
|
||||||
|
border-top: 1px solid #F1F5F9;
|
||||||
|
}
|
||||||
|
.campaign-terms-page .dt-info {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #64748B;
|
||||||
|
}
|
||||||
|
.campaign-terms-page .dt-paging .pagination {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none !important;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.campaign-terms-page .dt-paging .pagination .page-item {
|
||||||
|
list-style: none !important;
|
||||||
|
}
|
||||||
|
.campaign-terms-page .dt-paging .pagination .page-item .page-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 36px;
|
||||||
|
width: fit-content;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid #E2E8F0;
|
||||||
|
background: #FFFFFF;
|
||||||
|
color: #4E5E6A;
|
||||||
|
text-decoration: none;
|
||||||
|
line-height: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.campaign-terms-page .dt-paging .pagination .page-item.previous .page-link,
|
||||||
|
.campaign-terms-page .dt-paging .pagination .page-item.next .page-link {
|
||||||
|
min-width: 72px;
|
||||||
|
}
|
||||||
|
.campaign-terms-page .dt-paging .pagination .page-item .page-link:hover {
|
||||||
|
background: #EEF2FF;
|
||||||
|
color: #6690F4;
|
||||||
|
border-color: #6690F4;
|
||||||
|
}
|
||||||
|
.campaign-terms-page .dt-paging .pagination .page-item.active .page-link {
|
||||||
|
background: #6690F4;
|
||||||
|
color: #FFFFFF;
|
||||||
|
border-color: #6690F4;
|
||||||
|
}
|
||||||
|
.campaign-terms-page .dt-paging .pagination .page-item.disabled .page-link {
|
||||||
|
opacity: 0.35;
|
||||||
|
cursor: default;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var terms_ad_groups_table = null;
|
||||||
|
var terms_search_table = null;
|
||||||
|
var terms_negative_table = null;
|
||||||
|
|
||||||
|
var TERMS_STORAGE_CLIENT = 'campaign_terms.last_client_id';
|
||||||
|
var TERMS_STORAGE_CAMPAIGN = 'campaign_terms.last_campaign_id';
|
||||||
|
var TERMS_STORAGE_AD_GROUP = 'campaign_terms.last_ad_group_id';
|
||||||
|
|
||||||
|
function terms_storage_set( key, value )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if ( value === null || value === undefined || value === '' )
|
||||||
|
localStorage.removeItem( key );
|
||||||
|
else
|
||||||
|
localStorage.setItem( key, String( value ) );
|
||||||
|
}
|
||||||
|
catch ( e ) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function terms_storage_get( key )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return localStorage.getItem( key ) || '';
|
||||||
|
}
|
||||||
|
catch ( e )
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function format_num( value, digits )
|
||||||
|
{
|
||||||
|
var n = Number( value || 0 );
|
||||||
|
return n.toLocaleString( 'pl-PL', {
|
||||||
|
minimumFractionDigits: digits || 0,
|
||||||
|
maximumFractionDigits: digits || 0
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroy_table_if_exists( selector )
|
||||||
|
{
|
||||||
|
if ( $.fn.DataTable.isDataTable( selector ) )
|
||||||
|
{
|
||||||
|
$( selector ).DataTable().destroy();
|
||||||
|
$( selector + ' tbody' ).empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function build_ad_groups_table( rows )
|
||||||
|
{
|
||||||
|
destroy_table_if_exists( '#terms_ad_groups_table' );
|
||||||
|
|
||||||
|
terms_ad_groups_table = new DataTable( '#terms_ad_groups_table', {
|
||||||
|
data: rows || [],
|
||||||
|
processing: false,
|
||||||
|
serverSide: false,
|
||||||
|
autoWidth: false,
|
||||||
|
searching: false,
|
||||||
|
lengthChange: false,
|
||||||
|
pageLength: 15,
|
||||||
|
pagingType: 'simple_numbers',
|
||||||
|
order: [],
|
||||||
|
columns: [
|
||||||
|
{ data: 'ad_group_name', defaultContent: '' },
|
||||||
|
{ data: 'clicks_30', render: function( data, type ){ return type === 'display' ? format_num( data, 0 ) : Number( data || 0 ); } },
|
||||||
|
{ data: 'cost_30', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) : Number( data || 0 ); } },
|
||||||
|
{ data: 'conversion_value_30', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) : Number( data || 0 ); } },
|
||||||
|
{ data: 'roas_30', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) + '%' : Number( data || 0 ); } },
|
||||||
|
{ data: 'clicks_all_time', render: function( data, type ){ return type === 'display' ? format_num( data, 0 ) : Number( data || 0 ); } },
|
||||||
|
{ data: 'cost_all_time', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) : Number( data || 0 ); } },
|
||||||
|
{ data: 'conversion_value_all_time', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) : Number( data || 0 ); } },
|
||||||
|
{ data: 'roas_all_time', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) + '%' : Number( data || 0 ); } }
|
||||||
|
],
|
||||||
|
columnDefs: [
|
||||||
|
{ targets: 0, className: 'text-cell' },
|
||||||
|
{ targets: [1,5], className: 'num-cell', width: '80px' },
|
||||||
|
{ targets: [2,3,6,7], className: 'num-cell', width: '90px' },
|
||||||
|
{ targets: [4,8], className: 'num-cell', width: '85px' }
|
||||||
|
],
|
||||||
|
language: {
|
||||||
|
emptyTable: 'Brak danych do wyswietlenia',
|
||||||
|
info: 'Wpisy _START_ - _END_ z _TOTAL_',
|
||||||
|
paginate: { next: 'Dalej', previous: 'Wstecz' }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_search_terms_table( rows, negative_keywords )
|
||||||
|
{
|
||||||
|
destroy_table_if_exists( '#terms_search_table' );
|
||||||
|
|
||||||
|
var negative_set = {};
|
||||||
|
( negative_keywords || [] ).forEach( function( nk ) {
|
||||||
|
negative_set[ String( nk.keyword_text || '' ).toLowerCase().trim() ] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
terms_search_table = new DataTable( '#terms_search_table', {
|
||||||
|
data: rows || [],
|
||||||
|
processing: false,
|
||||||
|
serverSide: false,
|
||||||
|
autoWidth: false,
|
||||||
|
searching: true,
|
||||||
|
lengthChange: false,
|
||||||
|
pageLength: 15,
|
||||||
|
pagingType: 'simple_numbers',
|
||||||
|
order: [],
|
||||||
|
columns: [
|
||||||
|
{ data: 'search_term', defaultContent: '', width: '340px' },
|
||||||
|
{ data: 'ad_group_name', defaultContent: '', width: '360px' },
|
||||||
|
{ data: 'clicks_30', render: function( data, type ){ return type === 'display' ? format_num( data, 0 ) : Number( data || 0 ); } },
|
||||||
|
{ data: 'cost_30', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) : Number( data || 0 ); } },
|
||||||
|
{ data: 'conversion_value_30', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) : Number( data || 0 ); } },
|
||||||
|
{ data: 'roas_30', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) + '%' : Number( data || 0 ); } },
|
||||||
|
{ data: 'clicks_all_time', render: function( data, type ){ return type === 'display' ? format_num( data, 0 ) : Number( data || 0 ); } },
|
||||||
|
{ data: 'cost_all_time', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) : Number( data || 0 ); } },
|
||||||
|
{ data: 'conversion_value_all_time', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) : Number( data || 0 ); } },
|
||||||
|
{ data: 'roas_all_time', render: function( data, type ){ return type === 'display' ? format_num( data, 2 ) + '%' : Number( data || 0 ); } },
|
||||||
|
{ data: 'id', orderable: false, searchable: false, render: function( data, type, row ) {
|
||||||
|
if ( type !== 'display' ) return data;
|
||||||
|
return '<button type="button" class="terms-add-negative-btn" data-search-term-id="' + row.id + '" title="Dodaj do wykluczajacych"><i class="fa-solid fa-ban"></i></button>';
|
||||||
|
} }
|
||||||
|
],
|
||||||
|
columnDefs: [
|
||||||
|
{ targets: 0, className: 'text-cell phrase-nowrap', width: '220px' },
|
||||||
|
{ targets: 1, className: 'text-cell' },
|
||||||
|
{ targets: [2,6], className: 'num-cell', width: '70px' },
|
||||||
|
{ targets: [3,4,7,8], className: 'num-cell', width: '85px' },
|
||||||
|
{ targets: [5,9], className: 'num-cell', width: '80px' },
|
||||||
|
{ targets: 10, className: 'dt-center', width: '50px' }
|
||||||
|
],
|
||||||
|
createdRow: function( row, data ) {
|
||||||
|
var term = String( data.search_term || '' ).toLowerCase().trim();
|
||||||
|
if ( negative_set[ term ] )
|
||||||
|
{
|
||||||
|
$( row ).addClass( 'term-is-negative' );
|
||||||
|
}
|
||||||
|
},
|
||||||
|
language: {
|
||||||
|
emptyTable: 'Brak danych do wyswietlenia',
|
||||||
|
info: 'Wpisy _START_ - _END_ z _TOTAL_',
|
||||||
|
paginate: { next: 'Dalej', previous: 'Wstecz' }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var q = $( '#phrase_search' ).val();
|
||||||
|
terms_search_table.search( q ).draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_negative_terms_table( rows )
|
||||||
|
{
|
||||||
|
destroy_table_if_exists( '#terms_negative_table' );
|
||||||
|
|
||||||
|
terms_negative_table = new DataTable( '#terms_negative_table', {
|
||||||
|
data: rows || [],
|
||||||
|
processing: false,
|
||||||
|
serverSide: false,
|
||||||
|
autoWidth: false,
|
||||||
|
searching: true,
|
||||||
|
lengthChange: false,
|
||||||
|
pageLength: 15,
|
||||||
|
pagingType: 'simple_numbers',
|
||||||
|
order: [],
|
||||||
|
columns: [
|
||||||
|
{ data: 'scope', render: function( data, type, row ) {
|
||||||
|
var scope = data === 'ad_group' ? 'Ad group' : 'Kampania';
|
||||||
|
if ( data === 'ad_group' && row.ad_group_name )
|
||||||
|
scope = 'Ad group: ' + row.ad_group_name;
|
||||||
|
return scope;
|
||||||
|
} },
|
||||||
|
{ data: 'keyword_text', defaultContent: '', width: '340px' },
|
||||||
|
{ data: 'match_type', defaultContent: '' }
|
||||||
|
],
|
||||||
|
columnDefs: [
|
||||||
|
{ targets: 0, className: 'text-cell' },
|
||||||
|
{ targets: 1, className: 'text-cell phrase-nowrap' },
|
||||||
|
{ targets: 2, width: '120px' }
|
||||||
|
],
|
||||||
|
language: {
|
||||||
|
emptyTable: 'Brak danych do wyswietlenia',
|
||||||
|
info: 'Wpisy _START_ - _END_ z _TOTAL_',
|
||||||
|
paginate: { next: 'Dalej', previous: 'Wstecz' }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var q = $( '#phrase_search' ).val();
|
||||||
|
terms_negative_table.search( q ).draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset_all_tables()
|
||||||
|
{
|
||||||
|
build_ad_groups_table( [] );
|
||||||
|
build_search_terms_table( [] );
|
||||||
|
build_negative_terms_table( [] );
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_phrase_tables()
|
||||||
|
{
|
||||||
|
var campaign_id = $( '#terms_campaign_id' ).val();
|
||||||
|
var ad_group_id = $( '#terms_ad_group_id' ).val();
|
||||||
|
|
||||||
|
if ( !campaign_id )
|
||||||
|
{
|
||||||
|
reset_all_tables();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/campaign_terms/get_campaign_phrase_details/',
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
campaign_id: campaign_id,
|
||||||
|
ad_group_id: ad_group_id
|
||||||
|
},
|
||||||
|
success: function( response )
|
||||||
|
{
|
||||||
|
var data = JSON.parse( response );
|
||||||
|
var negative_keywords = data.negative_keywords || [];
|
||||||
|
build_search_terms_table( data.search_terms || [], negative_keywords );
|
||||||
|
build_negative_terms_table( negative_keywords );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_ad_groups()
|
||||||
|
{
|
||||||
|
var campaign_id = $( '#terms_campaign_id' ).val();
|
||||||
|
var $ad_group_select = $( '#terms_ad_group_id' );
|
||||||
|
var saved_ad_group_id = terms_storage_get( TERMS_STORAGE_AD_GROUP );
|
||||||
|
|
||||||
|
$ad_group_select.empty();
|
||||||
|
$ad_group_select.append( '<option value="">- wszystkie grupy -</option>' );
|
||||||
|
|
||||||
|
if ( !campaign_id )
|
||||||
|
{
|
||||||
|
build_ad_groups_table( [] );
|
||||||
|
load_phrase_tables();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/campaign_terms/get_campaign_ad_groups/',
|
||||||
|
type: 'POST',
|
||||||
|
data: { campaign_id: campaign_id },
|
||||||
|
success: function( response )
|
||||||
|
{
|
||||||
|
var data = JSON.parse( response );
|
||||||
|
var ad_groups = data.ad_groups || [];
|
||||||
|
build_ad_groups_table( ad_groups );
|
||||||
|
|
||||||
|
ad_groups.forEach( function( row ) {
|
||||||
|
$ad_group_select.append( '<option value="' + row.id + '">' + row.ad_group_name + '</option>' );
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( saved_ad_group_id && $ad_group_select.find( 'option[value="' + saved_ad_group_id + '"]' ).length )
|
||||||
|
{
|
||||||
|
$ad_group_select.val( saved_ad_group_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
load_phrase_tables();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_campaigns_for_client( restore_campaign_id )
|
||||||
|
{
|
||||||
|
var client_id = $( '#terms_client_id' ).val();
|
||||||
|
var $campaign_select = $( '#terms_campaign_id' );
|
||||||
|
|
||||||
|
$campaign_select.empty();
|
||||||
|
$campaign_select.append( '<option value="">- wybierz kampanie -</option>' );
|
||||||
|
|
||||||
|
if ( !client_id )
|
||||||
|
{
|
||||||
|
$( '#terms_ad_group_id' ).val( '' );
|
||||||
|
reset_all_tables();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/campaign_terms/get_campaigns_list/client_id=' + client_id,
|
||||||
|
type: 'GET',
|
||||||
|
success: function( response )
|
||||||
|
{
|
||||||
|
var data = JSON.parse( response );
|
||||||
|
var campaigns = Object.entries( data.campaigns || {} );
|
||||||
|
|
||||||
|
campaigns.sort( function( a, b ) {
|
||||||
|
var nameA = String( ( a[1] && a[1].campaign_name ) ? a[1].campaign_name : '' ).toLowerCase();
|
||||||
|
var nameB = String( ( b[1] && b[1].campaign_name ) ? b[1].campaign_name : '' ).toLowerCase();
|
||||||
|
if ( nameA === '--- konto ---' ) return -1;
|
||||||
|
if ( nameB === '--- konto ---' ) return 1;
|
||||||
|
if ( nameA > nameB ) return 1;
|
||||||
|
if ( nameA < nameB ) return -1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
campaigns.forEach( function( pair ) {
|
||||||
|
var value = pair[1];
|
||||||
|
$campaign_select.append( '<option value="' + value.id + '">' + value.campaign_name + '</option>' );
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( restore_campaign_id && $campaign_select.find( 'option[value="' + restore_campaign_id + '"]' ).length )
|
||||||
|
{
|
||||||
|
$campaign_select.val( restore_campaign_id );
|
||||||
|
}
|
||||||
|
|
||||||
|
load_ad_groups();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$( function()
|
||||||
|
{
|
||||||
|
$( 'body' ).on( 'change', '#terms_client_id', function()
|
||||||
|
{
|
||||||
|
var client_id = $( this ).val();
|
||||||
|
terms_storage_set( TERMS_STORAGE_CLIENT, client_id );
|
||||||
|
terms_storage_set( TERMS_STORAGE_CAMPAIGN, '' );
|
||||||
|
terms_storage_set( TERMS_STORAGE_AD_GROUP, '' );
|
||||||
|
load_campaigns_for_client( '' );
|
||||||
|
});
|
||||||
|
|
||||||
|
$( 'body' ).on( 'change', '#terms_campaign_id', function()
|
||||||
|
{
|
||||||
|
var campaign_id = $( this ).val();
|
||||||
|
terms_storage_set( TERMS_STORAGE_CAMPAIGN, campaign_id );
|
||||||
|
terms_storage_set( TERMS_STORAGE_AD_GROUP, '' );
|
||||||
|
$( '#terms_ad_group_id' ).val( '' );
|
||||||
|
load_ad_groups();
|
||||||
|
});
|
||||||
|
|
||||||
|
$( 'body' ).on( 'change', '#terms_ad_group_id', function()
|
||||||
|
{
|
||||||
|
terms_storage_set( TERMS_STORAGE_AD_GROUP, $( this ).val() );
|
||||||
|
load_phrase_tables();
|
||||||
|
});
|
||||||
|
|
||||||
|
$( 'body' ).on( 'click', '.terms-add-negative-btn', function()
|
||||||
|
{
|
||||||
|
var search_term_id = $( this ).data( 'search-term-id' );
|
||||||
|
|
||||||
|
$.confirm({
|
||||||
|
title: 'Dodaj do wykluczajacych',
|
||||||
|
columnClass: 'col-md-4 col-md-offset-4',
|
||||||
|
content:
|
||||||
|
'<div class="form-group" style="margin-bottom:10px;">' +
|
||||||
|
'<label for="negative_scope" style="display:block;margin-bottom:6px;">Poziom wykluczenia</label>' +
|
||||||
|
'<select id="negative_scope" class="form-control">' +
|
||||||
|
'<option value="campaign" selected>Kampania (zalecane)</option>' +
|
||||||
|
'<option value="ad_group">Grupa reklam</option>' +
|
||||||
|
'</select>' +
|
||||||
|
'<small style="display:block;margin-top:6px;color:#64748B;">Kampania = widoczne w wykluczeniach kampanii. Grupa reklam = tylko w tej grupie.</small>' +
|
||||||
|
'</div>' +
|
||||||
|
'<div class="form-group" style="margin-bottom:0;">' +
|
||||||
|
'<label for="negative_match_type" style="display:block;margin-bottom:6px;">Typ dopasowania</label>' +
|
||||||
|
'<select id="negative_match_type" class="form-control">' +
|
||||||
|
'<option value="PHRASE" selected>Dopasowanie do wyrazenia</option>' +
|
||||||
|
'<option value="EXACT">Dopasowanie scisle</option>' +
|
||||||
|
'<option value="BROAD">Dopasowanie przyblizone</option>' +
|
||||||
|
'</select>' +
|
||||||
|
'</div>',
|
||||||
|
type: 'blue',
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
text: 'Zapisz',
|
||||||
|
btnClass: 'btn-blue',
|
||||||
|
action: function()
|
||||||
|
{
|
||||||
|
var match_type = this.$content.find( '#negative_match_type' ).val() || 'PHRASE';
|
||||||
|
var scope = this.$content.find( '#negative_scope' ).val() || 'campaign';
|
||||||
|
var modal = this;
|
||||||
|
modal.showLoading( true );
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/campaign_terms/add_negative_keyword/',
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
search_term_id: search_term_id,
|
||||||
|
match_type: match_type,
|
||||||
|
scope: scope
|
||||||
|
},
|
||||||
|
success: function( response )
|
||||||
|
{
|
||||||
|
var data = JSON.parse( response );
|
||||||
|
modal.close();
|
||||||
|
|
||||||
|
if ( data.success )
|
||||||
|
{
|
||||||
|
var debugHtml = '';
|
||||||
|
if ( data.debug )
|
||||||
|
{
|
||||||
|
debugHtml = '<hr style="margin:10px 0;">' +
|
||||||
|
'<div style="font-size:11px;color:#666;text-align:left;">' +
|
||||||
|
'<strong>Debug:</strong><br>' +
|
||||||
|
'Customer ID: ' + ( data.debug.customer_id || 'brak' ) + '<br>' +
|
||||||
|
'Campaign ID: ' + ( data.debug.campaign_external_id || 'brak' ) + '<br>' +
|
||||||
|
'Ad Group ID: ' + ( data.debug.ad_group_external_id || 'brak' ) + '<br>' +
|
||||||
|
'Scope: ' + ( data.debug.scope || 'brak' ) + '<br>' +
|
||||||
|
'Keyword: ' + ( data.debug.keyword_text || 'brak' ) + '<br>' +
|
||||||
|
'API response: <pre style="font-size:10px;max-height:120px;overflow:auto;background:#f5f5f5;padding:6px;margin-top:4px;">' + JSON.stringify( data.debug.api_response, null, 2 ) + '</pre>' +
|
||||||
|
'Verification: <pre style="font-size:10px;max-height:120px;overflow:auto;background:#f5f5f5;padding:6px;margin-top:4px;">' + JSON.stringify( data.debug.verification, null, 2 ) + '</pre>' +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
var successDialog = $.alert({
|
||||||
|
title: 'Sukces',
|
||||||
|
columnClass: 'col-md-4 col-md-offset-4',
|
||||||
|
content: ( data.message || 'Fraza zostala dodana do wykluczajacych.' )
|
||||||
|
+ '<br><small style="display:block;margin-top:8px;color:#64748B;">Zmiana moze byc widoczna w panelu Google Ads po 1-3 minutach.</small>'
|
||||||
|
+ debugHtml,
|
||||||
|
type: 'green'
|
||||||
|
});
|
||||||
|
setTimeout( function() { successDialog.close(); }, 10000 );
|
||||||
|
load_phrase_tables();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var debugHtml = '';
|
||||||
|
if ( data.debug )
|
||||||
|
{
|
||||||
|
debugHtml = '<hr style="margin:10px 0;">' +
|
||||||
|
'<div style="font-size:11px;color:#666;text-align:left;">' +
|
||||||
|
'<strong>Debug:</strong><pre style="font-size:10px;max-height:150px;overflow:auto;background:#f5f5f5;padding:6px;">' + JSON.stringify( data.debug, null, 2 ) + '</pre>' +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
$.alert({
|
||||||
|
title: 'Blad',
|
||||||
|
columnClass: 'col-md-4 col-md-offset-4',
|
||||||
|
content: ( data.message || 'Nie udalo sie dodac frazy.' ) + ( data.error ? '<br><small>' + data.error + '</small>' : '' ) + debugHtml,
|
||||||
|
type: 'red'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function()
|
||||||
|
{
|
||||||
|
modal.close();
|
||||||
|
$.alert({
|
||||||
|
title: 'Blad',
|
||||||
|
columnClass: 'col-md-4 col-md-offset-4',
|
||||||
|
content: 'Blad komunikacji z serwerem.',
|
||||||
|
type: 'red'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancel: { text: 'Anuluj' }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$( 'body' ).on( 'input', '#phrase_search', function()
|
||||||
|
{
|
||||||
|
var q = $( this ).val();
|
||||||
|
if ( terms_search_table )
|
||||||
|
terms_search_table.search( q ).draw();
|
||||||
|
if ( terms_negative_table )
|
||||||
|
terms_negative_table.search( q ).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
reset_all_tables();
|
||||||
|
|
||||||
|
var saved_client_id = terms_storage_get( TERMS_STORAGE_CLIENT );
|
||||||
|
var saved_campaign_id = terms_storage_get( TERMS_STORAGE_CAMPAIGN );
|
||||||
|
|
||||||
|
if ( saved_client_id && $( '#terms_client_id option[value="' + saved_client_id + '"]' ).length )
|
||||||
|
{
|
||||||
|
$( '#terms_client_id' ).val( saved_client_id );
|
||||||
|
load_campaigns_for_client( saved_campaign_id );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -3,12 +3,11 @@
|
|||||||
<h2><i class="fa-solid fa-chart-line"></i> Kampanie</h2>
|
<h2><i class="fa-solid fa-chart-line"></i> Kampanie</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filtry -->
|
|
||||||
<div class="campaigns-filters">
|
<div class="campaigns-filters">
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<label for="client_id"><i class="fa-solid fa-building"></i> Klient</label>
|
<label for="client_id"><i class="fa-solid fa-building"></i> Klient</label>
|
||||||
<select id="client_id" name="client_id" class="form-control">
|
<select id="client_id" name="client_id" class="form-control">
|
||||||
<option value="">— wybierz klienta —</option>
|
<option value="">- wybierz klienta -</option>
|
||||||
<?php foreach ( $this -> clients as $client ): ?>
|
<?php foreach ( $this -> clients as $client ): ?>
|
||||||
<option value="<?= $client['id']; ?>"><?= htmlspecialchars( $client['name'] ); ?></option>
|
<option value="<?= $client['id']; ?>"><?= htmlspecialchars( $client['name'] ); ?></option>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -18,21 +17,19 @@
|
|||||||
<label for="campaign_id"><i class="fa-solid fa-bullhorn"></i> Kampania</label>
|
<label for="campaign_id"><i class="fa-solid fa-bullhorn"></i> Kampania</label>
|
||||||
<div class="filter-with-action">
|
<div class="filter-with-action">
|
||||||
<select id="campaign_id" name="campaign_id" class="form-control">
|
<select id="campaign_id" name="campaign_id" class="form-control">
|
||||||
<option value="">— wybierz kampanię —</option>
|
<option value="">- wybierz kampanie -</option>
|
||||||
</select>
|
</select>
|
||||||
<button type="button" id="delete_campaign" class="btn-icon btn-icon-delete" title="Usuń kampanię">
|
<button type="button" id="delete_campaign" class="btn-icon btn-icon-delete" title="Usun kampanie">
|
||||||
<i class="fa-solid fa-trash"></i>
|
<i class="fa-solid fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Wykres -->
|
|
||||||
<div class="campaigns-chart-wrap">
|
<div class="campaigns-chart-wrap">
|
||||||
<div id="container"></div>
|
<div id="container"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tabela historii -->
|
|
||||||
<div class="campaigns-table-wrap">
|
<div class="campaigns-table-wrap">
|
||||||
<table class="table" id="products">
|
<table class="table" id="products">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -40,22 +37,47 @@
|
|||||||
<th>Data</th>
|
<th>Data</th>
|
||||||
<th>ROAS (30 dni)</th>
|
<th>ROAS (30 dni)</th>
|
||||||
<th>ROAS (all time)</th>
|
<th>ROAS (all time)</th>
|
||||||
<th>Wartość konwersji (30 dni)</th>
|
<th>Wartosc konwersji (30 dni)</th>
|
||||||
<th>Wydatki (30 dni)</th>
|
<th>Wydatki (30 dni)</th>
|
||||||
<th>Komentarz</th>
|
<th>Komentarz</th>
|
||||||
<th>Strategia ustalania stawek</th>
|
<th>Strategia ustalania stawek</th>
|
||||||
<th>Budżet</th>
|
<th>Budzet</th>
|
||||||
<th style="width: 60px; text-align: center;">Akcje</th>
|
<th style="width: 60px; text-align: center;">Akcje</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody></tbody>
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var client_id = '';
|
var STORAGE_CLIENT_KEY = 'campaigns.last_client_id';
|
||||||
|
var STORAGE_CAMPAIGN_KEY = 'campaigns.last_campaign_id';
|
||||||
|
var restore_campaign_after_client_load = '';
|
||||||
|
|
||||||
|
function storage_set( key, value )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if ( value === null || value === undefined || value === '' )
|
||||||
|
localStorage.removeItem( key );
|
||||||
|
else
|
||||||
|
localStorage.setItem( key, String( value ) );
|
||||||
|
}
|
||||||
|
catch ( e ) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function storage_get( key )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return localStorage.getItem( key ) || '';
|
||||||
|
}
|
||||||
|
catch ( e )
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function reloadChart()
|
function reloadChart()
|
||||||
{
|
{
|
||||||
@@ -68,8 +90,8 @@ function reloadChart()
|
|||||||
data: { campaign_id: campaign_id },
|
data: { campaign_id: campaign_id },
|
||||||
success: function( response )
|
success: function( response )
|
||||||
{
|
{
|
||||||
const parsedData = JSON.parse( response );
|
var parsedData = JSON.parse( response );
|
||||||
let plotLines = [];
|
var plotLines = [];
|
||||||
|
|
||||||
parsedData.comments.forEach( function( comment ) {
|
parsedData.comments.forEach( function( comment ) {
|
||||||
plotLines.push({
|
plotLines.push({
|
||||||
@@ -140,11 +162,21 @@ function reloadChart()
|
|||||||
|
|
||||||
$( function()
|
$( function()
|
||||||
{
|
{
|
||||||
// Załaduj kampanie po wyborze klienta
|
|
||||||
$( 'body' ).on( 'change', '#client_id', function()
|
$( 'body' ).on( 'change', '#client_id', function()
|
||||||
{
|
{
|
||||||
client_id = $( this ).val();
|
var client_id = $( this ).val();
|
||||||
|
storage_set( STORAGE_CLIENT_KEY, client_id );
|
||||||
var campaigns_select = $( '#campaign_id' );
|
var campaigns_select = $( '#campaign_id' );
|
||||||
|
var campaign_to_restore = restore_campaign_after_client_load;
|
||||||
|
|
||||||
|
if ( !campaign_to_restore )
|
||||||
|
storage_set( STORAGE_CAMPAIGN_KEY, '' );
|
||||||
|
|
||||||
|
campaigns_select.empty();
|
||||||
|
campaigns_select.append( '<option value="">- wybierz kampanie -</option>' );
|
||||||
|
|
||||||
|
if ( !client_id )
|
||||||
|
return;
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/campaigns/get_campaigns_list/client_id=' + client_id,
|
url: '/campaigns/get_campaigns_list/client_id=' + client_id,
|
||||||
@@ -152,29 +184,33 @@ $( function()
|
|||||||
success: function( response )
|
success: function( response )
|
||||||
{
|
{
|
||||||
var data = JSON.parse( response );
|
var data = JSON.parse( response );
|
||||||
campaigns_select.empty();
|
var campaigns = Object.entries( data.campaigns || {} );
|
||||||
campaigns_select.append( '<option value="">— wybierz kampanię —</option>' );
|
|
||||||
|
|
||||||
var campaigns = Object.entries( data.campaigns );
|
|
||||||
|
|
||||||
campaigns.sort( function( a, b ) {
|
campaigns.sort( function( a, b ) {
|
||||||
if ( a[1] === "--- konto ---" ) return -1;
|
var nameA = String( ( a[1] && a[1].campaign_name ) ? a[1].campaign_name : '' ).toLowerCase();
|
||||||
if ( b[1] === "--- konto ---" ) return 1;
|
var nameB = String( ( b[1] && b[1].campaign_name ) ? b[1].campaign_name : '' ).toLowerCase();
|
||||||
return a[1] > b[1] ? 1 : ( a[1] < b[1] ? -1 : 0 );
|
if ( nameA === '--- konto ---' ) return -1;
|
||||||
|
if ( nameB === '--- konto ---' ) return 1;
|
||||||
|
if ( nameA > nameB ) return 1;
|
||||||
|
if ( nameA < nameB ) return -1;
|
||||||
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
campaigns.forEach( function( [key, value] ) {
|
campaigns.forEach( function( pair ) {
|
||||||
|
var value = pair[1];
|
||||||
campaigns_select.append( '<option value="' + value.id + '">' + value.campaign_name + '</option>' );
|
campaigns_select.append( '<option value="' + value.id + '">' + value.campaign_name + '</option>' );
|
||||||
});
|
});
|
||||||
|
|
||||||
<?php if ( $campaign_id ): ?>
|
if ( campaign_to_restore && campaigns_select.find( 'option[value="' + campaign_to_restore + '"]' ).length )
|
||||||
campaigns_select.val( '<?= $campaign_id; ?>' ).trigger( 'change' );
|
{
|
||||||
<?php endif; ?>
|
campaigns_select.val( campaign_to_restore ).trigger( 'change' );
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_campaign_after_client_load = '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Usuwanie kampanii
|
|
||||||
$( 'body' ).on( 'click', '#delete_campaign', function()
|
$( 'body' ).on( 'click', '#delete_campaign', function()
|
||||||
{
|
{
|
||||||
var campaign_id = $( '#campaign_id' ).val();
|
var campaign_id = $( '#campaign_id' ).val();
|
||||||
@@ -184,19 +220,19 @@ $( function()
|
|||||||
{
|
{
|
||||||
$.alert({
|
$.alert({
|
||||||
title: 'Uwaga',
|
title: 'Uwaga',
|
||||||
content: 'Najpierw wybierz kampanię do usunięcia.',
|
content: 'Najpierw wybierz kampanie do usuniecia.',
|
||||||
type: 'orange'
|
type: 'orange'
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$.confirm({
|
$.confirm({
|
||||||
title: 'Potwierdzenie usunięcia',
|
title: 'Potwierdzenie usuniecia',
|
||||||
content: 'Czy na pewno chcesz usunąć kampanię <strong>' + campaign_name + '</strong>?<br><br>Ta operacja jest nieodwracalna i usunie również całą historię kampanii.',
|
content: 'Czy na pewno chcesz usunac kampanie <strong>' + campaign_name + '</strong>?<br><br>Ta operacja jest nieodwracalna i usunie rowniez cala historie kampanii.',
|
||||||
type: 'red',
|
type: 'red',
|
||||||
buttons: {
|
buttons: {
|
||||||
confirm: {
|
confirm: {
|
||||||
text: 'Usuń',
|
text: 'Usun',
|
||||||
btnClass: 'btn-red',
|
btnClass: 'btn-red',
|
||||||
keys: ['enter'],
|
keys: ['enter'],
|
||||||
action: function()
|
action: function()
|
||||||
@@ -209,9 +245,12 @@ $( function()
|
|||||||
var data = JSON.parse( response );
|
var data = JSON.parse( response );
|
||||||
if ( data.success )
|
if ( data.success )
|
||||||
{
|
{
|
||||||
|
if ( storage_get( STORAGE_CAMPAIGN_KEY ) === String( campaign_id ) )
|
||||||
|
storage_set( STORAGE_CAMPAIGN_KEY, '' );
|
||||||
|
|
||||||
$.alert({
|
$.alert({
|
||||||
title: 'Sukces',
|
title: 'Sukces',
|
||||||
content: 'Kampania została usunięta.',
|
content: 'Kampania zostala usunieta.',
|
||||||
type: 'green',
|
type: 'green',
|
||||||
autoClose: 'ok|2000'
|
autoClose: 'ok|2000'
|
||||||
});
|
});
|
||||||
@@ -220,8 +259,8 @@ $( function()
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
$.alert({
|
$.alert({
|
||||||
title: 'Błąd',
|
title: 'Blad',
|
||||||
content: data.message || 'Nie udało się usunąć kampanii.',
|
content: data.message || 'Nie udalo sie usunac kampanii.',
|
||||||
type: 'red'
|
type: 'red'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -234,7 +273,6 @@ $( function()
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Usuwanie wpisu historii
|
|
||||||
$( 'body' ).on( 'click', '.delete-history-entry', function()
|
$( 'body' ).on( 'click', '.delete-history-entry', function()
|
||||||
{
|
{
|
||||||
var btn = $( this );
|
var btn = $( this );
|
||||||
@@ -242,12 +280,12 @@ $( function()
|
|||||||
var date = btn.data( 'date' );
|
var date = btn.data( 'date' );
|
||||||
|
|
||||||
$.confirm({
|
$.confirm({
|
||||||
title: 'Potwierdzenie usunięcia',
|
title: 'Potwierdzenie usuniecia',
|
||||||
content: 'Czy na pewno chcesz usunąć wpis z dnia <strong>' + date + '</strong>?',
|
content: 'Czy na pewno chcesz usunac wpis z dnia <strong>' + date + '</strong>?',
|
||||||
type: 'red',
|
type: 'red',
|
||||||
buttons: {
|
buttons: {
|
||||||
confirm: {
|
confirm: {
|
||||||
text: 'Usuń',
|
text: 'Usun',
|
||||||
btnClass: 'btn-red',
|
btnClass: 'btn-red',
|
||||||
keys: ['enter'],
|
keys: ['enter'],
|
||||||
action: function()
|
action: function()
|
||||||
@@ -262,18 +300,19 @@ $( function()
|
|||||||
{
|
{
|
||||||
$.alert({
|
$.alert({
|
||||||
title: 'Sukces',
|
title: 'Sukces',
|
||||||
content: 'Wpis został usunięty.',
|
content: 'Wpis zostal usuniety.',
|
||||||
type: 'green',
|
type: 'green',
|
||||||
autoClose: 'ok|2000'
|
autoClose: 'ok|2000'
|
||||||
});
|
});
|
||||||
$( '#products' ).DataTable().ajax.reload( null, false );
|
if ( $.fn.DataTable.isDataTable( '#products' ) )
|
||||||
|
$( '#products' ).DataTable().ajax.reload( null, false );
|
||||||
reloadChart();
|
reloadChart();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$.alert({
|
$.alert({
|
||||||
title: 'Błąd',
|
title: 'Blad',
|
||||||
content: data.message || 'Nie udało się usunąć wpisu.',
|
content: data.message || 'Nie udalo sie usunac wpisu.',
|
||||||
type: 'red'
|
type: 'red'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -286,13 +325,19 @@ $( function()
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Załaduj dane po wyborze kampanii
|
|
||||||
$( 'body' ).on( 'change', '#campaign_id', function()
|
$( 'body' ).on( 'change', '#campaign_id', function()
|
||||||
{
|
{
|
||||||
var campaign_id = $( this ).val();
|
var campaign_id = $( this ).val();
|
||||||
|
storage_set( STORAGE_CAMPAIGN_KEY, campaign_id );
|
||||||
|
|
||||||
table = $( '#products' ).DataTable();
|
if ( $.fn.DataTable.isDataTable( '#products' ) )
|
||||||
table.destroy();
|
{
|
||||||
|
$( '#products' ).DataTable().destroy();
|
||||||
|
$( '#products tbody' ).empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !campaign_id )
|
||||||
|
return;
|
||||||
|
|
||||||
new DataTable( '#products', {
|
new DataTable( '#products', {
|
||||||
ajax: {
|
ajax: {
|
||||||
@@ -316,11 +361,11 @@ $( function()
|
|||||||
{ width: '60px', name: 'actions', orderable: false, className: "dt-center" }
|
{ width: '60px', name: 'actions', orderable: false, className: "dt-center" }
|
||||||
],
|
],
|
||||||
language: {
|
language: {
|
||||||
processing: 'Ładowanie...',
|
processing: 'Ladowanie...',
|
||||||
emptyTable: 'Brak danych do wyświetlenia',
|
emptyTable: 'Brak danych do wyswietlenia',
|
||||||
info: 'Wpisy _START_ - _END_ z _TOTAL_',
|
info: 'Wpisy _START_ - _END_ z _TOTAL_',
|
||||||
infoEmpty: '',
|
infoEmpty: '',
|
||||||
lengthMenu: 'Pokaż _MENU_ wpisów',
|
lengthMenu: 'Pokaz _MENU_ wpisow',
|
||||||
paginate: {
|
paginate: {
|
||||||
first: 'Pierwsza',
|
first: 'Pierwsza',
|
||||||
last: 'Ostatnia',
|
last: 'Ostatnia',
|
||||||
@@ -332,5 +377,14 @@ $( function()
|
|||||||
|
|
||||||
reloadChart();
|
reloadChart();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var saved_client_id = storage_get( STORAGE_CLIENT_KEY );
|
||||||
|
var saved_campaign_id = storage_get( STORAGE_CAMPAIGN_KEY );
|
||||||
|
|
||||||
|
if ( saved_client_id && $( '#client_id option[value="' + saved_client_id + '"]' ).length )
|
||||||
|
{
|
||||||
|
restore_campaign_after_client_load = saved_campaign_id;
|
||||||
|
$( '#client_id' ).val( saved_client_id ).trigger( 'change' );
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<script type="text/javascript" src="/libraries/datepicker/js/datepicker.min.js"></script>
|
<script type="text/javascript" src="/libraries/datepicker/js/datepicker.min.js"></script>
|
||||||
<script type="text/javascript" src="/libraries/datepicker/js/i18n/datepicker.pl.js"></script>
|
<script type="text/javascript" src="/libraries/datepicker/js/i18n/datepicker.pl.js"></script>
|
||||||
<script type="text/javascript" src="/libraries/daterange/daterangepicker.js"></script>
|
<script type="text/javascript" src="/libraries/daterange/daterangepicker.js"></script>
|
||||||
<script type="text/javascript" src="/libraries/jquery-confirm/jquery-confirm.min.js"></script>
|
<script type="text/javascript" src="/libraries/adspro-dialog.js"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
<link rel="Stylesheet" type="text/css" href="/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.structure.min.css">
|
<link rel="Stylesheet" type="text/css" href="/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.structure.min.css">
|
||||||
<link rel="Stylesheet" type="text/css" href="/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.theme.min.css">
|
<link rel="Stylesheet" type="text/css" href="/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.theme.min.css">
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<link rel="Stylesheet" type="text/css" href="/libraries/framework/vendor/plugins/daterange/daterangepicker.css">
|
<link rel="Stylesheet" type="text/css" href="/libraries/framework/vendor/plugins/daterange/daterangepicker.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/libraries/datepicker/css/datepicker.min.css">
|
<link rel="stylesheet" type="text/css" href="/libraries/datepicker/css/datepicker.min.css">
|
||||||
<link rel="Stylesheet" type="text/css" href="/libraries/daterange/daterangepicker.css">
|
<link rel="Stylesheet" type="text/css" href="/libraries/daterange/daterangepicker.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/libraries/jquery-confirm/jquery-confirm.min.css">
|
<link rel="stylesheet" type="text/css" href="/libraries/adspro-dialog.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/layout/style.css">
|
<link rel="stylesheet" type="text/css" href="/layout/style.css">
|
||||||
</head>
|
</head>
|
||||||
<body class="logged">
|
<body class="logged">
|
||||||
|
|||||||
@@ -21,14 +21,14 @@
|
|||||||
<script src="/libraries/framework/vendor/plugins/moment/pl.js"></script>
|
<script src="/libraries/framework/vendor/plugins/moment/pl.js"></script>
|
||||||
<script src="/libraries/framework/vendor/plugins/datepicker/js/bootstrap-datetimepicker.js"></script>
|
<script src="/libraries/framework/vendor/plugins/datepicker/js/bootstrap-datetimepicker.js"></script>
|
||||||
<script src="/libraries/framework/vendor/plugins/daterange/daterangepicker.js"></script>
|
<script src="/libraries/framework/vendor/plugins/daterange/daterangepicker.js"></script>
|
||||||
<script src="/libraries/jquery-confirm/jquery-confirm.min.js"></script>
|
<script src="/libraries/adspro-dialog.js">
|
||||||
<script src="/libraries/select2/js/select2.full.min.js"></script>
|
<script src="/libraries/select2/js/select2.full.min.js"></script>
|
||||||
<script src="/libraries/functions.js"></script>
|
<script src="/libraries/functions.js"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.1.7/css/dataTables.bootstrap5.min.css">
|
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.1.7/css/dataTables.bootstrap5.min.css">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/plugins/datepicker/css/bootstrap-datetimepicker.css">
|
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/plugins/datepicker/css/bootstrap-datetimepicker.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/plugins/daterange/daterangepicker.css">
|
<link rel="stylesheet" type="text/css" href="/libraries/framework/vendor/plugins/daterange/daterangepicker.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/libraries/jquery-confirm/jquery-confirm.min.css">
|
<link rel="stylesheet" type="text/css" href="/libraries/adspro-dialog.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/libraries/select2/css/select2.min.css">
|
<link rel="stylesheet" type="text/css" href="/libraries/select2/css/select2.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/libraries/select2/css/select2-bootstrap-5-theme.min.css">
|
<link rel="stylesheet" type="text/css" href="/libraries/select2/css/select2-bootstrap-5-theme.min.css">
|
||||||
<link rel="stylesheet" type="text/css" href="/layout/style.css">
|
<link rel="stylesheet" type="text/css" href="/layout/style.css">
|
||||||
@@ -58,6 +58,12 @@
|
|||||||
<span>Kampanie</span>
|
<span>Kampanie</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="<?= $module === 'campaign_terms' ? 'active' : '' ?>">
|
||||||
|
<a href="/campaign_terms">
|
||||||
|
<i class="fa-solid fa-list-check"></i>
|
||||||
|
<span>Grupy i frazy</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="<?= $module === 'products' ? 'active' : '' ?>">
|
<li class="<?= $module === 'products' ? 'active' : '' ?>">
|
||||||
<a href="/products">
|
<a href="/products">
|
||||||
<i class="fa-solid fa-box-open"></i>
|
<i class="fa-solid fa-box-open"></i>
|
||||||
@@ -111,6 +117,7 @@
|
|||||||
<?php
|
<?php
|
||||||
$breadcrumbs = [
|
$breadcrumbs = [
|
||||||
'campaigns' => 'Kampanie',
|
'campaigns' => 'Kampanie',
|
||||||
|
'campaign_terms' => 'Grupy i frazy',
|
||||||
'products' => 'Produkty',
|
'products' => 'Produkty',
|
||||||
'clients' => 'Klienci',
|
'clients' => 'Klienci',
|
||||||
'allegro' => 'Allegro import',
|
'allegro' => 'Allegro import',
|
||||||
|
|||||||
Reference in New Issue
Block a user