Korzystanie z bazy danych WordPress

Blisko dwa i pół roku temu napisałem obszerny (blisko 45 000 znaków) artykuł o tworzeniu wtyczek w WordPress. Zanim zaczniesz czytać polecam zapoznać się z nim, albo przynajmniej przejrzeć i posiłkować się podczas czytania.

WordPress standardowo instalowany jest w bazie danych MySQL (najczęściej). Jeżeli nie wiesz czym jest SQL to poniższy artykuł będzie trudny do przyswojenia – wyjaśniałem podstawy języka SQL na przykładzie SQLite. MySQL nieznacznie różni się od wspomnianego SQLite, ale generalnie wszystkie opierają się na tym samym języku SQL, a omawiany MySQL ma przede wszystkim bogatszą składnię (więcej możliwości).

I wreszcie – tematyką wtyczki, którą zrobimy będzie licznik odwiedzin wraz ze statystykami, który omawiałem w krótkim artykule Ruch przychodzący w PHP. No to zaczynajmy.

Co warto znać przed rozpoczęciem artykułu?

Czego się nauczysz?

  • korzystania z SQL w WordPress

Opis działania

Zgodnie z przytoczonym artykułem, zbierane będą adresy IP użytkowników, którzy odwiedzą nasz serwis (oraz oczywiście daty odwiedzin). Dodatkowo posiłkując się zewnętrznym API od http://ip-api.com/, sprawdzimy kraj danego użytkownika. API jest darmowe dla użytku niekomercyjnego. Chcąc uzyskać dokładniejsze dane umożliwimy włączenie śledzenia za pomocą plików cookie. Pamiętajmy jednak, że aby było to możliwe, użytkownik musi wyrazić zgodę.

Dane dotyczące odwiedzin powinny być wyświetlane na wykresie. Ponadto warto dać możliwość wyboru zakresu czasowego. Skorzystamy przy tym z technologii Ajax, aby dane zmieniały się dynamicznie bez wymaganego odświeżania strony.

Możliwości wtyczki

Zanim zaczniemy warto wypisać wszystkie możliwości jakie ma mieć nasza wtyczka.

  • unikalne odwiedziny w wybranym zakresie dat (domyślnie od początku uruchomienia wtyczki)
  • unikalne odwiedziny pogrupowane po krajach
  • ilość stałych użytkowników serwisu
  • odwiedziny na artykułach
  • witryny odsyłające
  • możliwość włączenia ciasteczek do sprawdzania odwiedzin
  • możliwość zachowania danych po usunięciu wtyczki

Nagłówek wtyczki

Kod nagłówka wraz z nazwą wtyczki:

/**
 * Plugin Name:       Prosty licznik odwiedzin od eskim.pl
 * Plugin URI:        https://eskim.pl/korzystanie-z-bazy-danych-wordpress/
 * Description:       Przykład tworzenia wtyczek w WordPress na podstawie kursu https://eskim.pl/korzystanie-z-bazy-danych-wordpress/
 * Version:           1.01
 * Requires at least: 5.2
 * Requires PHP:      5.6
 * Author:            Maciej Włodarczak
 * Author URI:        https://eskim.pl
 * License:           GPL v3 or later
 * Text Domain:       eskim_pl_views_counter
 * Domain Path:       /languages
 */

if ( !function_exists( 'add_action' ) ) {

	echo 'Zapraszam do artykułu <a href="https://eskim.pl/zapisywanie-w-bazie-danych-wordpressa/">Zapisywanie w bazie danych WordPressa</a>';
	exit;
}

Na końcu dodajemy sprawdzenie istnienia jednej z funkcji WordPress, aby upewnić się że wtyczka została wywołana z menu, a nie z zewnątrz.

Tworzenie tabel

Zanim zaczniemy zastanówmy się jakie tabele będą nam potrzebne.

Adresy IP odwiedzających

Po adresie IP rozpoznamy czy ktoś nas już odwiedził. Możemy także sprawdzić jakie strony przeglądał w naszej witrynie. Nie jest to rozwiązanie idealne, gdyż da nieco fałszywe wyniki jeżeli odwiedzający posiada zmienne IP, które jest całkiem powszechne w Polsce. Dodatkowo chcemy sprawdzić w jakim kraju znajduje się użytkownik. Aby jednak nie tracić przestrzeni, w tabeli z adresami IP umieścimy liczbę wskazującą na dany kraj. Podobnie zrobimy z witrynami odsyłającymi

Nasza tabela zawierać będzie:

  • adres IP
  • numer wpisu
  • identyfikator kraju wskazujący na tabelę z państwami
  • datę utworzenia

Spójrzmy na pseudo-zapytanie SQL tworzące tabelę

CREATE TABLE visitors (
  ip BIGINT,
  post INT NULL,
  country INT NULL,
  referer INT NULL,
  created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
  index (ip),
  index (post),
  FOREIGN KEY (country) REFERENCES countries (id),
  FOREIGN KEY (referer) REFERENCES referers (id)
)

Powyżej tworzymy tabelę w której:

  • adres IP (ip) jest liczbą (adres IP można łatwo zamienić na wartość int w PHP). Dodatkowo dodaliśmy indeksowanie, by szybko wyszukiwać po tej kolumnie
  • identyfikator wpisu (post) – na wszelki wypadek umożliwiamy wpisanie wartości null. Również dodaliśmy indeksowanie.
  • identyfikator kraju (country) jest kluczem obcym, czyli wskazuje na inną tabelę w której musi istnieć zapisany identyfikator (jeżeli z jakiegoś powodu nie znamy kraju to będzie tu wartość null)
  • data utworzenia (created) dodawana jest automatycznie przy dodawaniu rekordu

Kraj odwiedzających

Czyli wspomniany kraj z którego jest odwiedzający – uzyskany za pomocą API.

CREATE TABLE countries (
  id INT,
  name VARCHAR(50),
  code VARCHAR(10),
  PRIMARY KEY (id)
)

W powyższej tabeli z krajami dodajemy:

  • identyfikator kraju (id) – który jest kluczem głównym (unikalnym, niepowtarzalnym)
  • nazwę kraju (name)
  • kod kraju (code) np. PL – API też zwraca tą informację

Witryna odsyłająca

Adres witryny z jakiej przyszedł do nas użytkownik zapiszemy w osobnej tabeli (jest ograniczona ilość takich adresów w końcu).

CREATE TABLE referers (
  id INT,
  url VARCHAR (255)
)

Aktualnie ta wartość nie zawsze jest uzupełniana przez przeglądarki.

Ciasteczka

Dodatkowo chcemy mieć możliwość umieszczania ciasteczek u użytkownika (oczywiście jeżeli wyrazi na to zgodę). Możemy w tym celu wygenerować unikatowy identyfikator, który zapiszemy w ciasteczku. To w połączeniu z adresem IP pozwoli nam zidentyfikować użytkownika nawet jeżeli ma zmienne IP. Należy pamiętać, że ciasteczka nie są wieczne. Użytkownik może sam je usunąć, mogą zostać usunięte po pewnym czasie przez mechanizmy przeglądarki, albo skończy się ich ważność.

Jeżeli chodzi o unikatowy identyfikator to gdybyśmy mieli nieskończoną pamięć moglibyśmy skorzystać np. ze 128 bitowego UUID. Niemniej łatwo policzyć, że tak duży identyfikator może szybko zapełnić bazę danych. Dlatego musimy pomyśleć nad czymś mniejszym – proponuję po prostu adres IP zapisany w formie liczby.

Idea jest taka, że w momencie wykrycia ciasteczka z innym IP, niż obecnie – aktualizujemy poprzednie adresy IP użytkownika w bazie. Nie jest to rozwiązanie idealne, ale w znacznym przypadku okaże się skuteczne.

Tworzenie tabel w WordPress

Tworzenie tabel powinno być robione podczas instalacji wtyczki. W naszym przypadku zrobimy to podczas jej włączania, ale zmodyfikujemy zapytanie tak, by nie tworzyła tabeli jeżeli ta istnieje.

Zanim utworzymy tabele, musimy dodać skrypt, który zawiera funkcję dbDelta

require_once(ABSPATH . 'wp-admin/includes/upgrade.php');

Następnie tworzymy tabele, dodając do funkcji zmienną globalną $wpdb

if ( !function_exists('eskim_pl_views_counter_activation') ) :
function eskim_pl_views_counter_activation() {

	global $wpdb;

	$table_name_countries = $wpdb->prefix . 'eskim_pl_views_counter_countries';

	$sql = "CREATE TABLE IF NOT EXISTS $table_name_countries (
			id INT NOT NULL AUTO_INCREMENT,
			name VARCHAR (50),
			code VARCHAR (10),
			PRIMARY KEY (id)
		);";
	
	dbDelta( $sql );


	$table_name_referers = $wpdb->prefix . 'eskim_pl_views_counter_referers';

	$sql = "CREATE TABLE IF NOT EXISTS $table_name_referers (
			id INT NOT NULL AUTO_INCREMENT,
			url VARCHAR (255),
			PRIMARY KEY (id)
		);";

	dbDelta( $sql );

	$table_name_visitors = $wpdb->prefix . 'eskim_pl_views_counter_visitors';

	$sql = "CREATE TABLE IF NOT EXISTS $table_name_visitors (
			ip BIGINT,
			post INT NULL,
			country INT NULL,
			referer INT NULL,
			created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
			index (ip),
			index (post),
			FOREIGN KEY (referer) REFERENCES $table_name_referers (id),
			FOREIGN KEY (country) REFERENCES $table_name_countries (id)
		);";

	dbDelta( $sql );
}
endif;

$wpdb->prefix zwraca prefiks używany przez konkretną instancję WordPressa (ustawialiśmy go podczas instalacji – standardowo jest to wp_). Oznacza to, że wszystkie tabele zaczynają się od tego słowa. Warto trzymać się tej zasady, choć oczywiście nie musimy – można mieć np. wiele instancji WordPressa w jednej bazie i np. skrypt zliczający odwiedziny na wszystkich stronach korzystających z tej samej tabeli.

dbDelta – specjalne polecenie WordPressa służące do tworzenia lub modyfikacji tabel . W parametrze podajemy kod SQL.

Nazwy tworzonych tabel są dosyć długie. Ma to związek z tym, że istnieje szansa, że podobne nazwy już istnieją – dodane przez inne wtyczki (gdybyśmy je uprościli). Z tego powodu zalecam dodawać jakiś prefiks lub postfiks do nazwy.

Ponadto istotna jest jeszcze kolejność tworzenia tabel, gdyż w głównej tabeli (z adresami IP) odwołujemy się do innych tabel, które muszą już istnieć.

Pobieranie i zapisywanie danych

Kiedy użytkownik odwiedzi naszą stronę pozostawia ślad w postaci adresu IP, adresu odsyłającego lub wspomnianego ciasteczka. Więcej szczegółów znajduje się w artykule Ruch przychodzący w PHP.

Dodawanie rekordów

Spójrzmy na kod dodający rekordy do bazy. Korzystamy z metod insert należącej do $wpdb

if ( !function_exists('eskim_pl_views_counter_visitor_add ') ) :
function eskim_pl_views_counter_visitor_add ($ip, $post = null, $country = null, $referer = null) {

	global $wpdb;

	$table_name = $wpdb->prefix . 'eskim_pl_views_counter_visitors';

	return $wpdb->insert( $table_name,
	[
		'ip' => $ip,
		'post' => $post,
		'country' => $country,
		'referer' => $referer
	]);
}
endif;


if ( !function_exists('eskim_pl_views_counter_country_add') ) :
function eskim_pl_views_counter_country_add ($name, $code) {

	global $wpdb;

	$table_name = $wpdb->prefix . 'eskim_pl_views_counter_countries';

	$wpdb->insert( $table_name,
	[
		'name' => $name,
		'code' => $code
	]);
	
	return $wpdb->insert_id;
}
endif;


if ( !function_exists('eskim_pl_views_counter_referer_add') ) :
function eskim_pl_views_counter_referer_add ($url) {

	global $wpdb;

	$table_name = $wpdb->prefix . 'eskim_pl_views_counter_referers';

	$wpdb->insert( $table_name,
	[
		'url' => $url
	]);
	
	return $wpdb->insert_id;
}
endif;

Zwróć uwagę, że nie dodajemy pola id oraz created w tabelach w których występują, bo te są automatycznie dodawane przez bazę danych. Dodatkowo metody dodające kraj i stronę odsyłającą zwracają wartość id dodanego rekordu.

Pobieranie id kraju oraz strony odsyłającej

Dodajmy funkcję, która pobierze identyfikator kraju dla danego adresu IP – o ile już wcześniej istniał w bazie. Robimy to po to, by nie wywoływać nadmiarowo zapytań do API pobierającego nazwy i kod kraju. To samo zróbmy ze stroną odsyłającą, ale tym razem po nazwie.

if ( !function_exists('eskim_pl_country_get_by_ip') ) :
function eskim_pl_country_get_by_ip ($ip) {	

	global $wpdb;

	$table_name_vistors = $wpdb->prefix . 'eskim_pl_views_counter_visitors';
	$table_name_countries = $wpdb->prefix . 'eskim_pl_views_counter_countries';

	$query = $wpdb->prepare ("
		SELECT $table_name_countries.id
		FROM $table_name_vistors 
		JOIN $table_name_countries ON $table_name_vistors.country = $table_name_countries.id
		WHERE ip = %d", 
		$ip
	  );

	return $wpdb->get_var ($query);  
}
endif;

if ( !function_exists('eskim_pl_country_get_referer_by_url') ) :
function eskim_pl_country_get_referer_by_url ($url) {	

	global $wpdb;

	$table_name = $wpdb->prefix . 'eskim_pl_views_counter_referers';

	$query = $wpdb->prepare ("
		SELECT id
		FROM $table_name
		WHERE url = %s", 
		$url
	  );

	return $wpdb->get_var ($query);  
}
endif;

Korzystamy z metody prepare, której celem jest przygotowanie zapytania do wysyłki do bazy. Dla pobieranego kraju funkcja oczekuje, że pod zmienną ip znajdzie się liczba (%d). W przypadku strony odsyłającej jest to wartość tekstowa (%s). Ponieważ w obu przypadkach pobieramy tylko jedno pole możemy użyć metody get_var, która zwraca wartość pojedynczego pola lub null.

Pobieranie danych o kraju z API

Skorzystajmy z API http://ip-api.com i pobierzmy dane w formacie JSON.

if ( !function_exists('eskim_pl_country_api') ) :
function eskim_pl_country_api ($ip) {	

	global $wpdb;

	$data = file_get_contents ( "http://ip-api.com/json/$ip" );
	return json_decode ($data, true);
}
endif;

Pobieranie informacji o użytkowniku

Mamy już wszystkie potrzebne funkcje, by pobrać dane o użytkowniku i później dodać je do bazy

if ( !function_exists('eskim_pl_count_visitors') ) :
function eskim_pl_count_visitors() {

  $postId = get_the_ID();
	if ($postId == 0) return false;

	$ip = ip2long ( $_SERVER['REMOTE_ADDR'] );

	$countryId = eskim_pl_country_get_by_ip ($ip);
	if ($countryId === null) {
		
		$country = eskim_pl_country_api ( $_SERVER['REMOTE_ADDR'] );
		$countryId = eskim_pl_views_counter_country_add ($country['country'], $country['countryCode']);
	}

	$refererId = null;
	if (isset ($_SERVER['HTTP_REFERER']) && !empty ($_SERVER['HTTP_REFERER']) ) {
		$refererId = eskim_pl_country_get_referer_by_url ($_SERVER['HTTP_REFERER']);
		if ($refererId === null) {
			
			$refererId = eskim_pl_views_counter_referer_add ($_SERVER['HTTP_REFERER']);
		}
	}

	return eskim_pl_views_counter_visitor_add ($ip, $postId, $countryId, $refererId);
}
endif;

Używane funkcje napisaliśmy już wcześniej. Doszły jedynie:

get_the_ID() – zwraca numer wpisu na aktualnej stronie, a jeżeli nie jest to strona z postem to kończy funkcję (nie musimy tego robić – złapiemy w ten sposób wszystkie wejścia na stronę logowania itp.). Dobrze zresztą przenieść tą funkcjonalność na zewnątrz funkcji, aby móc nią sterować.

ip2long($ip) – zamienia IP na liczbę (integer) dla „łatwiejszego” zapisu w bazie danych

Zmiana adresu IP w bazie

Kiedy wykryjemy ciasteczko zamienimy wszystkie wcześniejsze adresy IP użytkownika na bieżący adres. Adres IP nas nie interesuje, a jedynie identyfikacja użytkownika, więc taka operacja mimo, iż nie jest do końca poprawna (następuje „zakłamanie” danych) to w naszym przypadku się sprawdzi i będzie po prostu proste.

Dodajmy podmianę zmianę adresu IP na aktualne w przypadku wykrycia ciasteczka.

if ( !function_exists('eskim_pl_views_counter_visitor_update_ip') ) :
function eskim_pl_views_counter_visitor_update_ip ($oldip, $ip) {

  if ($oldip == $ip) return false;

  global $wpdb;

  $table_name = $wpdb->prefix . 'eskim_pl_views_counter_visitors';

	return $wpdb->update( $table_name,
		[ 'ip' => $oldip ],
		[' ip' => $ip ],
		[' %d '],
    [' %d ']
	);
}
endif;

Skorzystaliśmy z metody update, która:

  • w pierwszy parametrze przyjmuje to co ma zostać znalezione
  • w drugim to na co ma zostać zamienione
  • w trzecim – formaty przekazywanych danych w pierwszym parametrze (tutaj liczba %d)
  • w czwartym – formaty przekazywanych danych w drugim parametrze (tutaj liczba %d)

Utworzenie i odczyt ciasteczka

Tworzymy ciasteczko za pomocą setcookie w której podajemy nazwę, przechowywane dane oraz czas ważności ciasteczka.

if ( !function_exists('eskim_pl_views_counter_set_cookie ') ) :
function eskim_pl_views_counter_set_cookie ($ip) {

	setcookie ( 'eskim_pl_visit', $ip, time() + (86400 * 400) );
}
endif;

setcookie przyjmuje w pierwszym parametrze nazwę ciasteczka, później dane jakie mają zapisane, a na końcu czas po jakim ciacho zostanie usunięte (tutaj 400 dni)

Zapisaną wartość ciasteczka odczytujemy sprawdzając zmienną globalną $_COOKIE.

if ( !function_exists('eskim_pl_views_counter_get_cookie ') ) :
function eskim_pl_views_counter_get_cookie () {

	if ( !isset($_COOKIE['eskim_pl_visit']) ) return null;
	return $_COOKIE['eskim_pl_visit'];
}
endif;

Podpięcie licznika pod stronę

Korzystamy z mechanizmu akcji WordPressa opisanym szczegółowo w artykule o tworzeniu wtyczek w WordPress. Tutaj wykorzystamy jedną z końcowych akcji np. wp_footer. Dodatkowo dodamy ograniczenie, by zliczanie odwiedzin nie uwzględniało osób, które mają uprawnienia do edytowania strony.

add_action('wp_footer', function () {
		
	if ( !current_user_can('edit_others_posts') ) eskim_pl_count_visitors ();
});

Po tej operacji nasz licznik będzie zbierał dane.

Jeżeli chcemy możemy wewnątrz funkcji podpiąć również obsługę ciasteczek, ale pamiętajmy, że nie powinniśmy tego robić, jeżeli użytkownik nie wyraził na to zgody.

$ip = ip2long ( $_SERVER['REMOTE_ADDR'] );
if ($cookie = eskim_pl_views_counter_get_cookie !== null) {
  eskim_pl_views_counter_visitor_update_ip ($cookie, $ip);
}

eskim_pl_views_counter_set_cookie ($ip);

Jeżeli użytkownik ma ciasteczko to podmieniamy w bazie adres IP. Później zapisujemy / aktualizujemy ciasteczko na komputerze. Można tutaj nieco rozwinąć i sprawdzać wartość ciasteczka – nie zapisywać go, jeżeli nie ma takiej potrzeby.

Analiza danych

Zastanówmy się co chcemy analizować i pobierzmy stosowne dane z bazy. Przedstawię tutaj pseudo-kod w SQL:

-- unikalne odwiedziny
SELECT COUNT( DISTINCT ip) AS count
FROM $table_name_vistors 
WHERE created BETWEEN %s AND %s

-- unikalne odwiedziny po kraju
SELECT name, COUNT( DISTINCT ip) AS count 
FROM $table_name_vistors
JOIN $table_name_countries
ON $table_name_vistors.country = $table_name_countries.id
WHERE created BETWEEN %s AND %s
GROUP BY $table_name_countries.id

-- strony polecające
SELECT url, COUNT( DISTINCT ip) AS count
FROM $table_name_vistors
JOIN $table_name_referers
ON $table_name_vistors.referer = $table_name_referers.id
WHERE created BETWEEN %s AND %s
GROUP BY $table_name_referers.id

Powyższe zapytania wymagają nieco większej znajomości samego SQL-a. W artykule o SQLite nie wyjaśniłem wszystkiego, a doszło tutaj kilka nowych konstrukcji;

DISTINCT – wyciąga tylko unikatowe wartości

JOIN (...) ON (domyślnie jest to INNER JOIN) – służy do łączenia tabel, w tego rodzaju połączeniach możemy korzystać z danych z dwóch tabel połączonych dowolną kolumną (w przypadku INNER JOIN (tutaj) dane muszą być takie same w obu kolumnach).

WHERE – podajemy warunki jakie muszą spełniać dane – przykładowo data utworzenia między jedną, a drugą.

GROUP BY – grupowanie usuwa zdublowane dane np. grupowanie po ip oznacza, że zostanie zwrócony jeden unikalny rekord dla każdego adresu IP – niezależnie ile identycznych wpisów będzie w bazie.

Kod w PHP może wyglądać tak:

if ( !function_exists('eskim_pl_views_counter_get_unique_visitors_count') ) :
function eskim_pl_views_counter_get_unique_visitors_count ($dateFrom = null, $dateTo = null) {
	
	global $wpdb;
	
	$table_name = $wpdb->prefix . 'eskim_pl_views_counter_visitors';
	
	if ($dateFrom === null) $dateFrom = date('Y-m-d',0);
	if ($dateTo === null) $dateTo = date('Y-m-d',time()+86400);
	
	$query = $wpdb->prepare ("
	
			SELECT COUNT( DISTINCT ip) AS count
			FROM eskim_pl_views_counter_visitors 
			WHERE created BETWEEN %s AND %s",
			$dateFrom,
			$dateTo
		);	  
			  
	return $wpdb->get_var($query);
	
}
endif;

if ( !function_exists('eskim_pl_views_counter_get_unique_visitors_by_country') ) :
function eskim_pl_views_counter_get_unique_visitors_by_country ($dateFrom = null, $dateTo = null) {
	
	global $wpdb;
	
	if ($dateFrom === null) $dateFrom = date('Y-m-d',0);
	if ($dateTo === null) $dateTo = date('Y-m-d',time()+86400);
	
	$table_name_vistors = $wpdb->prefix . 'eskim_pl_views_counter_visitors';
	$table_name_countries = $wpdb->prefix . 'eskim_pl_views_counter_countries';
	
	$query = $wpdb->prepare ("
			SELECT name, COUNT( DISTINCT ip) AS count 
			FROM $table_name_vistors
			JOIN $table_name_countries
			ON $table_name_vistors.country = $table_name_countries.id
			WHERE created BETWEEN %s AND %s
			GROUP BY id",
			$dateFrom,
			$dateTo
		);	    
	return $wpdb->get_results($query);
	
}
endif;

if ( !function_exists('eskim_pl_views_counter_get_unique_visitors_by_referers') ) :
function eskim_pl_views_counter_get_unique_visitors_by_referers ($dateFrom = null, $dateTo = null) {
	
  global $wpdb;

	if ($dateFrom == null) $dateFrom = date('Y-m-d',0);
	if ($dateTo == null) $dateTo = date('Y-m-d',time()+86400);
	
	$table_name_vistors = $wpdb->prefix . 'eskim_pl_views_counter_visitors';
	$table_name_referers = $wpdb->prefix . 'eskim_pl_views_counter_referers';
	
	$query = $wpdb->prepare ("
			SELECT url, COUNT( DISTINCT ip) AS count
			FROM $table_name_vistors
			JOIN $table_name_referers
			ON $table_name_vistors.referer = $table_name_referers.id
			WHERE created BETWEEN %s AND %s
			GROUP BY id",
			$dateFrom,
			$dateTo
		);	  
			  
	return $wpdb->get_results($query);
	
}
endif;

Jeżeli nie podano daty to pobieramy dane od początku do dnia jutrzejszego. Dodatkowo doszła funkcja get_result, która zwraca dane w formie tablicy wyników.

Dodanie menu

To teraz dodajmy menu (ponownie odsyłam do wspomnianego artykułu)

add_action('admin_menu', function () {

    add_menu_page(
		__('Views counter','views-counter'), // tytuł strony
		__('Views counter','views-counter'), // tytuł w menu
		'manage_options',  // widoczne tylko osobom, które mogą zmieniać ustawienia
		'menu-views-counter', // identyfikator
		'eskim_pl_views_counter_menu_main', // funkcja, którą zostanie wywołana po kliknięciu
		'',               // link do ikony
		20                // pozycja w menu
    );

	add_submenu_page(
		'menu-views-counter',          // menu do którego się podpinasz
		__('Chars counter','views-counter').' - '.__('statistics','views-counter'), // tytuł strony
		__('Counter table','views-counter'),  // tytuł w menu
		'manage_options',               // widoczne tylko osobom, które mogą zmieniać ustawienia
		'submenu-views-counter-statistics', // identyfikator
		'eskim_pl_views_counter_menu_statistics', // funkcja, którą zostanie wywołana po kliknięciu
	);

});

Dodanie powyższego menu jeszcze nie zadziała, bo wcześniej musimy dodać stosowne funkcje.

if ( !function_exists('eskim_pl_views_counter_menu_main') ) :
function eskim_pl_views_counter_menu_main () {

    ?>
    <div class="wrap">
    <h1 class="wp-heading-inline"><?php echo esc_html (get_admin_page_title()); ?></h1>
    <hr class="wp-header-end">
    <p>Wtyczka wyświetla licznik odwiedzin</p>
    </div>
    <?php
	
}
endif;

if ( !function_exists('eskim_pl_views_counter_menu_statistics') ) :
function eskim_pl_views_counter_menu_statistics () {

    ?>
    <div class="wrap">
    <h1 class="wp-heading-inline"><?php echo esc_html (get_admin_page_title()); ?></h1>
    <hr class="wp-header-end">
    <p>Statystyki</p>
    </div>
    <?php
	
}
endif;

W pierwszej funkcji umieszczamy podstawowe informacje o wtyczce, choć oczywiście można menu podpiąć gdzieś indziej (np. narzędzia itp.). W drugiej funkcji – statystki, które dodasz samodzielnie korzystając z przykładowych zapytań SQL.

Usuwanie tabel

Usunięcie pluginu powinno wiązać się z czyszczeniem tabel (niektóre pluginy zostawiają dane po odinstalowaniu). Jeżeli chcemy usunąć tabele to moglibyśmy podpiąć funkcję pod register_uninstall_hook, ale problem w tym, że my nie instalowaliśmy wtyczki. W takiej sytuacji stosowny kod możemy umieścić w powyższym menu.

W menu utwórzmy przycisk, który po naciśnięciu wywoła przygotowany dalej kod czyszczący. Przycisk (formularz) wskazuje na plik admin-post.php i dodatkowo umieszcza losowe wartości w polach input typu hidden za pomocą funkcji wp_nonce_field.

admin-post.php po otrzymaniu poniższego formularza sprawdzi co znajduje się w polu input o nazwie action oraz sprawdzi czy wspomniane losowe wartości zgadzają się. W ten sposób chronimy się przed próbami wywoływania kodu przez tzw. scipt kids.

	$admin_url = admin_url('admin-post.php');

	echo '<form action="' . $admin_url . '" method="post">';
	echo '<input type="hidden" name="action" value="eskim_pl_views_counter_delete" />';
	wp_nonce_field ('eskim_pl_views_counter_delete_nonce');
	submit_button ('Usuń tabele i wyłącz wtyczkę');
	echo '</form>';

Dodajmy teraz funkcję oraz akcje, których celem będzie czyszczenie tabel, wyłączenie wtyczki i przekierowanie na menu z wtyczkami. Istotna jest kolejność usuwania tabel, gdyż mamy klucze obce w tabeli z odwiedzinami (ta musi być usunięta najpierw).

if ( !function_exists('eskim_pl_views_counter_delete') ) :
function eskim_pl_views_counter_delete () {

	check_admin_referer( 'eskim_pl_views_counter_delete_nonce' );

	global $wpdb;

	$table_name = $wpdb->prefix . 'eskim_pl_views_counter_visitors';
	$wpdb->query( "DROP TABLE IF EXISTS $table_name" );

	$table_name = $wpdb->prefix . 'eskim_pl_views_counter_countries';
	$wpdb->query( "DROP TABLE IF EXISTS $table_name" );

	$table_name = $wpdb->prefix . 'eskim_pl_views_counter_referers';
	$wpdb->query( "DROP TABLE IF EXISTS $table_name" );

	deactivate_plugins('eskim_pl_views_counter/eskim_pl_views_counter.php');
	
	wp_redirect (admin_url('plugins.php'));
}
endif;

add_action('admin_post_eskim_pl_views_counter_delete', 'eskim_pl_views_counter_delete');

Funkcje podpinamy pod akcje obsługiwane przez admin-post.php (zwróć uwagę na nazwę akcji i na pole value w formularzu z name ustawionym na action) Na początku skryptu sprawdzamy czy pola nonse są zgodne. Na końcu zaś wyłączamy plugi i przekierowujemy do menu z wtyczkami.

Skrypt dostępny na GitHub – views-counter-wordpress-plugin

Co dalej?

Powyższy kurs wystarczy do stworzenia działającego licznika odwiedzin. Plugin korzysta z bazy danych i warto mieć na uwadze, że jeżeli mocno obciążymy bazę to cała strona może zacząć działać wolniej lub w skrajnych przypadkach się zawiesić. Z tego powodu wskazane jest ograniczenie ilości zapisywanych danych np. sprawdzanie czy użytkownik już dzisiaj nas nie odwiedził. Wszystko zależy od tego jak dużo informacji potrzebujemy.

Ponadto tworzenie i usuwanie tabel powinno się znaleźć przy instalowaniu i odinstalowaniu wtyczki, a nie stosując jakieś magiczne przyciski. Użytkownik korzystający z naszej wtyczki musi mieć świadomość, że jego dane przepadną i dlatego procedura nie powinna być uruchamiana przy włączaniu / wyłączaniu.

Po więcej funkcji związanych z obsługą bazy warto zajrzeć do oficjalnej dokumentacji wpdb.

Podsumowanie

  1. Tworzenie tabel w WordPress można wykonać z użyciem dedykowanej funkcji dbDelta, ale najpierw trzeba dołączyć skrypt ją zawierający require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
  2. Do obsługi bazy danych WordPress służy dostępny obiekt $wpdb.
  3. $wpdb->prefix zwraca prefiks do tabel w danej instancji WordPressa.
  4. Dodawanie rekordu w tabeli odbywa się z użyciem funkcji $wpdb->insert, a numer ostatniego rekordu uzyskamy wywołując $wpdb->insert_id.
  5. Aktualizowanie rekordu w tabeli można zrobić z wykorzystaniem funkcji $wpdb->update.
  6. Przy użyciu $wpdb->prepare możemy (nie musimy) przygotować zapytanie do wysłania – robi się to w celu zwiększenia bezpieczeństwa – szczególnie jak zapytanie zawiera dane „z zewnątrz”.
  7. Wykonywanie dowolnych zapytań do bazy odbywa się z wykorzystaniem $wpdb->query.
  8. Odczytywanie wyników można zrealizować na kilka sposobów – get_var – zwraca jedno pole, get_col – kolumnę, get_row – rekord, get_results – wszystkie wyniki w tablicy
  9. Aby programowo wyłączyć wtyczkę możemy użyć funkcji deactivate_plugins.