run Python script from PHP <– PHP <– JavaScript (3)

Co do tej pory robiliśmy? Przesyłaliśmy dane w postaci nazwy miejscowości i jej współrzędnych …zaraz zaraz, a czy sama nazwa miejscowości nie wystarczy? Gdyby tak (rozmarzymy się…) mieś bazę wszystkich miejscowości i ich współrzędnych (w Polsce to około 103tyś sprecyzowanych miejsc, >4000 jednostek administracyjnych i prawie 400 powiatów i miast -opisuje to TERYT).

Gdyby mieć taką bazę i po prostu zapytać o współrzędne?

Można też zapytać o to Google -udostępnia taką usługę, – miejsce na osobny wpis -jak odpytać Googla z miejscowości aby zbudować sobie taką bazę…

Spróbujmy zrobić coś takiego, aby wpisując nazwę miejscowości otrzymać jej współrzędne.

Na Django w wersji podstawowej może to wyglądać tak:

http://django.wiks.eu/lookfor

Działanie jest takie, że po każdej naszej akcji – wpisaniu kolejnego znaku nazwy z klawiatury, JavaScript wzmocniona AngularJS odpytuje server fragmentem nazwy i uaktualnia zawartość wyświetlanej strony. Zobaczcie:

Na powyższej stronie – korzystającej z własnej bazy danych, w której są wszystkie 103tyś miejsc TERYT, a około 50tyś z nich ma przypisaną pozycję geograficzną (zweryfikowane tylko miasta (ok. 400szt) i powiaty).

Ale ok. Skupmy się na AngularJS. – frameworku napisanym w JavaScript.

Jak wygląda nasz schemat?

AngularJS – czyli program napisany w JavaScript przez programistów Google, działa na naszym lokalnym komputerze i monitoruje zmiany. Każde wprowadzenie kolejnego znaku z klawiatury do formularza wywołuje reakcje -Angular wysyła pytanie do serwera z nowymi wartościami, serwer odpowiada, -zapewne na podstawie zawartości bazy danych, odpowiedź wraca do Angular i jest odpowiednio dysponowana -gdy wymaga tego – modyfikuje zawartość HTML strony i przeglądarka na komputerze wyświetla nam stronę inaczej. W opisywanym przykładzie początkowo informuje nas o ilości pasujących nazw, następnie – gdy jest ich już dość mało – wyświetla je. Na końcu wybieramy jedną i ukazuje się np. mapka.

W projekcie którym się bawimy, chcemy uzyskać współrzędne geograficzne miejscowości o wprowadzanej nazwie.

To co trzeba w takim wypadku zrobić to:

1. utworzyć formularze – widoki w HTML,

2. oprogramować to po stronie Angular/JavaScript – aby Angular wiedział, czego oczekujemy,

3. oprogramować serwer, aby na zadawane przez Angular pytania odpowiadał -również w sposób jaki chcemy.

Serwer może być napisany w czymkolwiek, nawet prosty skrypt PHP na lokalnym komputerze może dostarczyć sporo frajdy.

run Python script from PHP (2)

Pomyślałem sobie, skoro umiem testować to i wywołać ze strony www poprzez PHP, to może pójść krok dalej? JavaScript?

Da to większe możliwości operacyjności stroną, dzięki temu JavaScript – strona www zyska moc Pythona. No oczywiście – można to zrobić poprzez Django. Ale nie zawsze mamy server Django, czasem po prostu PHP/Apache, który można w ten sposób nomen-omen ubogacić…

Skoro wiem już jak to hula w środku, usunąłem wyświetlanie komentarzy z Pythona i wewnętrznego PHP.

skrypt wewnętrzny Pythona mógłby wyglądać tak:

#!/usr/bin/env python
# coding=utf-8

import os, sys
from datetime import datetime
from time import sleep
import logging.handlers

log_main = logging.getLogger('main')
formatter = logging.Formatter('%(asctime)s.%(msecs)03d  %(levelname)-8s  %(name)-15s  %(message)s',
                       datefmt='%Y-%m-%d %H:%M:%S')
file_handler = logging.handlers.RotatingFileHandler(os.path.join(
                                          os.path.dirname(os.path.abspath(__file__)),
                                          'log.txt'
                                       ),
                                       maxBytes=1000000,
                                       backupCount=3)
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
log_main.setLevel(logging.DEBUG)
log_main.addHandler(file_handler)
log_main.addHandler(stream_handler)

log_main.info(u'start ' + unicode(datetime.now()) + u' ...')

args = {}
if sys.argv:
    for pair in sys.argv:
        one = pair.split('=', 1)
        if one and len(one) == 2 and one[0]:
            args[one[0].decode('utf-8')] = one[1].decode('utf-8')
args[u'pdt'] = unicode(datetime.now())
args[u'p3g'] = u'Pythona 3 grosze'
sleep(3)
print(args)

log_main.info(u'stop ' + unicode(datetime.now()) + u' ...')

Wprowadzone zmiany to:

– dołożony log w lokalnym pliku <code>log.txt</code> dzięki czemu wiemy co się wewnątrz dzieje, -tutaj jedynie wpisuję czasy startu i stopu, ale można cokolwiek 🙂

– Python dokłada swoje dane do otrzymanych data wejściowych -tutaj pola ‚p3g’ i ‚pdt’

– zakładam, że procedura Pythona będzie trwała chwilę, zanim zwróci wynik do JavyScript (Python –> PHP –> JavaScript) -tutaj 3 sekundy stąd dodane sleep(3)

Tworzenie logu, wiąże się z koniecznością dostępu do pliku logu (folderu w którym jest/będzie utworzony plik logu) – trzeba więc zadbać o uprawnienia do tego katalogu ( chown/chmod )

Właściwie, aby było to (jeszcze) ładniej, obuduję to w klasę:

#!/usr/bin/env python
# coding=utf-8

import os, sys
from datetime import datetime
from time import sleep
import logging.handlers


class PyMain:
    """

    """

    def __init__(self):
        """

        """

        self.log_main = logging.getLogger('main')
        formatter = logging.Formatter('%(asctime)s.%(msecs)03d  %(levelname)-8s  %(name)-15s  %(message)s',
                                      datefmt='%Y-%m-%d %H:%M:%S')
        file_handler = logging.handlers.RotatingFileHandler(os.path.join(
            os.path.dirname(os.path.abspath(__file__)),
            'log.txt'
        ),
            maxBytes=1000000,
            backupCount=3)
        file_handler.setFormatter(formatter)
        stream_handler = logging.StreamHandler()
        stream_handler.setFormatter(formatter)
        self.log_main.setLevel(logging.DEBUG)
        self.log_main.addHandler(file_handler)
        self.log_main.addHandler(stream_handler)
        self.log_main.info(u'init ' + unicode(datetime.now()))

    def clue(self):
        """
        return dict inomming plus some new fields
        :return:
        """

        self.log_main.info(u'start ' + unicode(datetime.now()) + u' ...')

        args = {}
        if sys.argv:
            for pair in sys.argv:
                one = pair.split('=', 1)
                if one and len(one) == 2 and one[0]:
                    args[one[0].decode('utf-8')] = one[1].decode('utf-8')
        args[u'pdt'] = unicode(datetime.now())
        args[u'p3g'] = u'Pythona 3 grosze'
        sleep(3)
        self.log_main.info(u'stop ' + unicode(datetime.now()) + u' ...')
        # print(args)
        return args


if __name__ == "__main__":
    c = PyMain()
    print(c.clue())

Musiałem zmienić z print(args) na return args, print zaś dopiero po wyjściu z metody.

Teraz pora na PHP odpalające …i też odrobina zmian:

<?php
header("Access-Control-Allow-Origin: *");

$log = 'log.txt';
$tolog = 'start';
file_put_contents($log, (new DateTime())->format('Y-m-d H:i:s').' '.$tolog.PHP_EOL, FILE_APPEND | LOCK_EX);

$python_filename = 'php_fired2.py';
$python_dir_path = '/home/wiks/Dokumenty/projects/weather/python2';

$raw_data = file_get_contents('php://input'); 
$data = json_decode( $raw_data, true ); 
$cli_data = ''; 
if($data) { 
    foreach($data as $key => $value) { 
        $cli_data .= '"'.$key.'='.$value.'" '; 
    } 
} 
$command = $python_dir_path.'/'.$python_filename.' '.$cli_data; 
$output_from_python = shell_exec($command); 
echo $output_from_python; 
$tolog = print_r($output_from_python, true); 
file_put_contents($log, (new DateTime())->format('Y-m-d H:i:s').' '.$tolog.PHP_EOL, FILE_APPEND | LOCK_EX); 
$tolog = 'stop'; 
file_put_contents($log, (new DateTime())->format('Y-m-d H:i:s').' '.$tolog.PHP_EOL, FILE_APPEND | LOCK_EX);

Zmiany:

– dodałem log zamiast echo, tak więc interesujące momenty, wartości i zachowania będą umieszczone w pliku log.txt;

– linijka header("Access-Control-Allow-Origin: *"); umożliwiająca dostęp skryptowi JavaScript do tego pliku. Wcześniej dostęp nie był potrzebny, bo usyskiwał go skrypt PHP będący na tym samym serwerze. Teraz dostęp uzyskuje JavaScript – działająca gdziekolwiek na komputerze użytkownika…

 

Właściwie 🙂 , aby było to (jeszcze) ładniej, obuduję to w klasę:

<?php
header("Access-Control-Allow-Origin: *");

class PhpFiredPython {
/**
 *
 */

/** ustala plik logu, zapisuje moment startu
 * ustala zmienne dla klasy
 */
public function __construct() {

    $this->filelog = 'log.txt';
    $this->log('>start');
    $this->python_filename = 'php_fired2.py';
    $this->python_dir_path = '/home/wiks/Dokumenty/projects/weather/python2';
}

/** prowadzi plik logu
 *
 * @param type $tolog
 */
private function log($tolog) {

    file_put_contents($this->filelog,
            (new DateTime())->format('Y-m-d H:i:s').' '.
            $tolog.
            PHP_EOL,
            FILE_APPEND | LOCK_EX);
}

/** wykonuje właściwą pracę - odpala skrypt Pythona
 *
 * @return type
 */
public function get_post_and_fire_python() {

    $raw_data = file_get_contents('php://input');
    $data = json_decode( $raw_data, true );
    $cli_data = '';
    if($data) {
        foreach($data as $key => $value) {
            $cli_data .= '"'.$key.'='.$value.'" ';
        }
    }
    $command = $this->python_dir_path.'/'.$this->python_filename.' '.$cli_data;
    $output_from_python = shell_exec($command);
    $this->log(print_r($output_from_python, true));
    $this->log('>stop');
    //echo $output_from_python;
    return $output_from_python;
    }

}

$c = new PhpFiredPython();
echo $c->get_post_and_fire_python();

Podobnie jak z Pythonem zamieniłem echo na return.

Do szczęścia tego etapu potrzeba jeszcze JavaScript. która to wszystko potrząśnie:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="resp">miejsce na odpowiedź</div>
<?php
require 'url_and_data.php';
$dataj = json_encode( $data );
?>
<script>
    var d1 = new Date();
    console.log('JS start: ' + d1.toLocaleTimeString());
    var xhr = new XMLHttpRequest();
    xhr.open('POST', '<?php echo $url; ?>', true);
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.onload = function () {
        var resp = this.responseText;
        console.log(resp);
        var element = document.getElementById("resp");
        if (resp) {
            element.innerHTML = resp;
            var d2 = new Date();
            console.log('JS stop: ' + d2.toLocaleTimeString());
        }
    };
    xhr.send('<?php echo $dataj; ?>');
</script>
</body>
</html>

Co my tu mamy:

Miejsce w HTML, do którego JS wpisze wynik: <div id="resp">miejsce na odpowiedź</div>, czyli DIV identyfikowany poprzez id=”resp” ,  oraz skrypt, który wywoła XMLHttpRequest metodą POST, na adres -nieładnie wdrukowany przez PHP: <?php echo $url; ?>  i wyśle tam dane w postaci JSON -również niełądnie wdrukowane przez PHP.

Po otrzymaniu zaś odpowiedzi (a będzie to czas który ustaliliśmy na 3 sekundy w skrypcie Pythona) w element DIV o id-„resp” wpisze otrzymaną odpowiedź. Ta sama odpowiedź pojawi się w konsoli przeglądarki (te miejsca, w których wpisujemy console.log(… ).

Na konsoli Firefoxa wygląda to podobnie do:

Albo nawet:

Gdzie ładnie widać co JS wysyła (dane formularza) i co otrzymuje (przedostatnia linijka).

Jak teraz wygląda nasz schemat?

A jak działa?

Otwieramy stronę PHP/HTML, przeglądarka tworzy element DIV z zawartością ‚miejsce na odpowiedź’.

Po wczytaniu skryptu JavaScript wykonuje go, czyli: wysyła dane JSON utworzone przez PHP na adres również utworzony przez PHP.

Dane trafiają do servera i wywołują skrypt PHP. Skrypt wywołuje skrypt Pythona, ten zaś po 3 skundach zwraca odpowiedź -też w formie JSON.

Odpowiedź trafia do PHP a następnie jest zwracana do JavaScript, który po jej odebraniu wykonuje
xhr.onload = function () { odszukuje element DOM o ID=”resp” i wpisuje otrzymaną odpowiedź do jego wnętrza jako HTML.

…to właściwie został mały kroczek do AngularJS, kiedyś to opiszę. Początek będzie zapewne wyglądał tak…

…run Python script from PHP

Kolejnym krokiem – chciałem sprawdzić to co wcześniej udało się osiągnąć, ale w ten sposób, aby miast schematu Python <–> PHP <–> Python użyć PHP <–> PHP <–> Python. Idąc dalej, można sobie wyobrazić, że to pierwsze PHP to strona z której wysyłamy zapytanie do (drugiego) PHP etc…

Rozbudowałem także część opisową, teraz schemat wygląda tak:

kod końcowego skryptu – Pythona:

Python wczytuje argumenty podane przez wywołujący program w linii komend, są to pary klucz=wartość, rozdzielone spacją, sys.argv odczytuje je jako listę, przy czym na pierwszym miejscu [0] występuje ścieżka pliku Pythona.
Dodałem też przedstawienie się versji Pythona (będzie to ważne przy testowaniu np. z venv) i moment startu i końca działania skryptu (wrócimy do tego później).

#!/usr/bin/env python
# coding=utf-8

import sys
from datetime import datetime

print 'Python: start: ' + str(datetime.now()) + '<br>'
args = {}
if sys.argv:
   for pair in sys.argv:
      #print pair
      #print '<br>'
      one = pair.split('=', 1)
      if one and len(one) == 2 and one[0]:
         #print one[0]
         #print '<br>'
         args[one[0].decode('utf-8')] = one[1].decode('utf-8')
print 'Python: Python-version: ' + str(sys.version) + '<br>'
print 'Python: RX-DATA:<br>'
print(args)
print '<br>Python: stop: ' + str(datetime.now()) + '<br>'
print 'Python: tested by: http://blog.wiks.eu'

wyjście Pythona może wyglądać np tak:

Python: start: 2018-05-12 09:10:49.836750
Python: Python-version: 2.7.9 (default, Aug 13 2016, 16:41:35) [GCC 4.9.2]
Python: RX-DATA:
{u'city_name': u'Cz\u0119stochowa', u'lat': u'50.7651978', u'lon': u'19.1587636'}
Python: stop: 2018-05-12 09:10:49.836967
Python: tested by: http://blog.wiks.eu 

kod (środkowego/)’kopiącego’ PHP:
Podobnie jak w Pythonie – przedstawiamy się versją i mierzymy czas.
Wyświetlamy także sposób w jaki wywołujemy Pythona i to co nam Python zwrócił.

<?php
/* PHP running Python
 */
echo 'PHP: PHPversion: '.phpversion().'<br>'."\n"; // 5.6.33-0+deb8u1
echo 'PHP: start: '.date("Y-m-d H:i:s.").gettimeofday()['usec'].'<br>'."\n";
$raw_data = file_get_contents('php://input');
echo 'PHP: RX RAW-DATA: '.gettype($raw_data).' --> '; // string
print_r($raw_data); // {"arg1": "tralala", "arg2": 123.45, "arg3": "unicode str ąśćęłóźć "}
echo "<br>"."\n";
$data = json_decode( $raw_data, true );
echo 'PHP: JSON DECODED: '.gettype($data).' --> '; // string
print_r( $data );
echo "<br>"."\n";
$cli_data = '';
if($data){
    foreach($data as $key => $value) {
        $cli_data .= '"'.$key.'='.$value.'" ';
    }
}
$python_dir_path = '/home/wiks/Dokumenty/projects/weather/python2';
$python_filename = 'php_fired.py';
$command = $python_dir_path.'/'.$python_filename.' '.$cli_data;
echo 'PHP: COMMAND-LINE for Python: '.$command."<br>"."\n";
$output_from_python = shell_exec($command);
echo 'PHP: RX from Python: <br>'."\n";
echo '--------<br>'."\n";
echo $output_from_python;
echo "\n".'<br>--------<br>'."\n";
echo 'PHP: stop: '.date("Y-m-d H:i:s.").gettimeofday()['usec'].'<br>'."\n";
echo 'PHP: tested by: http://blog.wiks.eu'."\n";
?>

kod (pierwszego) PHP/html:
PHP osadzony w kodzie HTML -nadaje się do odpalenia ze strony www. Sprawdziłem działanie na localhost i na zdalnym host -oba postawione na Apache2.

Pobieramy URL i dane do wysłania require 'url_and_data.php';, przedstawiamy się versją, zamieniamy array wejściową na JSON, wysyłamy – przy pomocy cURL /niże też bez cURL/, wyświetlamy to co odebrano. Voila:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>test PHP fired Python</title>
</head>
<body>
<?php
require 'url_and_data.php';
echo 'PHP-TEST: PHPversion: '.phpversion().'<br>';
echo 'PHP-TEST: ARRAY TO SEND: ';
print_r($data);
echo '<br>';
$dataj = json_encode( $data );
echo 'PHP-TEST: TX JSON_ENCODE --> ';
print_r($dataj);
echo ' --> TYPE: '.gettype($dataj).'<br>'; // string

$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, array("Content-type: application/json"));
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $dataj);
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ( $status != 200 ) {
    die("Error: call to URL $url failed with status $status, response $result, curl_error " . curl_error($curl) . ", curl_errno " . curl_errno($curl));
}
curl_close($curl);
print 'PHP-TEST: RX-result: (with curl:)<br>';

print '------------<br>';
print($result);
print '<br>------------<br>';
print 'PHP-TEST: tested by: http://blog.wiks.eu';
?>
</body>
</html>

Pod require 'url_and_data.php' kryją się definicje URL i DATA do przesłania, np:

<?php 
$url = 'http://localhost/run_python.php'; 
$data = array('city_name' => 'Częstochowa',
              'lat' => '50.7651978', 
              'lon' => '19.1587636'
              );        

w powyższych danych przykładowo – przesyłam nazwę miejscowości i współrzędne geograficzne…

Można to oczywiście też wykonać bez cURL`a:

echo ' --> TYPE: '.gettype($dataj).'<br>'; // string

// use key 'http' even if you send the request to https://...
$options = array(
    'http' => array(
        'header'  => "Content-type: application/x-www-form-urlencoded\r\n",
        'method'  => 'POST',
        'content' => $dataj 
    )
);
$context  = stream_context_create($options);
$result = file_get_contents($url, false, $context);
if ($result === FALSE) { // Handle error  
}
print 'PHP-TEST: RX-result: (curl-less:)<br>';

wynik działania:

PHP-TEST: PHPversion: 5.6.33-0+deb8u1
PHP-TEST: ARRAY TO SEND: Array ( [city_name] => Częstochowa [lat] => 50.7651978 [lon] => 19.1587636 )
PHP-TEST: TX JSON_ENCODE --> {"city_name":"Cz\u0119stochowa","lat":"50.7651978","lon":"19.1587636"} --> TYPE: string
PHP-TEST: RX-result: (with curl:)
------------
PHP: PHPversion: 5.6.33-0+deb8u1
PHP: start: 2018-05-12 09:10:49.818623
PHP: RX RAW-DATA: string --> {"city_name":"Cz\u0119stochowa","lat":"50.7651978","lon":"19.1587636"}
PHP: JSON DECODED: array --> Array ( [city_name] => Częstochowa [lat] => 50.7651978 [lon] => 19.1587636 )
PHP: COMMAND-LINE for Python: /home/wiks/Dokumenty/projects/JS_PHP_Python/clue/python/php_fired.py "city_name=Częstochowa" "lat=50.7651978" "lon=19.1587636"
PHP: RX from Python:
--------
Python: start: 2018-05-12 09:10:49.836750
Python: Python-version: 2.7.9 (default, Aug 13 2016, 16:41:35) [GCC 4.9.2]
Python: RX-DATA:
{u'city_name': u'Cz\u0119stochowa', u'lat': u'50.7651978', u'lon': u'19.1587636'}
Python: stop: 2018-05-12 09:10:49.836967
Python: tested by: http://blog.wiks.eu
--------
PHP: stop: 2018-05-12 09:10:49.838728
PHP: tested by: http://blog.wiks.eu
------------
PHP-TEST: tested by: http://blog.wiks.eu

…a gdyby tak zrobić to z JavaScript?

Mając już wyobrażenie przeniesione do kodu, działające (jakoś), pora zastanowić się jak przenieść je do WordPressa i jak wykonać możliwość ustawień, kolorów, czasów, treści etc.

Zaczniemy od przerzucenia kodu do pluginu PHP.

WordPressowe pluginy działają w ten sposób, że kod zapisany w PHP, umieszczamy w folderze i dalej w:

/wp-content/plugins

Folder i plik PHP będą miały jednakową nazwę – ‚wiks_comment_encourage’ i ‚wiks_comment_encourage.php’. Znajdą się tam również podfoldery ‚img’, ‚css’ i ‚js’ z -odpowiednio – plikami obrazów, stylów i skryptów.
Możemy je umieścić przy pomocy FTP.
Plik php musi mieć nagłówek, który spowoduje rozpoznanie go jako pluginu. Np. taką:

/*
    Plugin Name: WikS-comment encourage
    Plugin URI: http://blog.wiks.eu/wp-plugin-zacheta-do-zostawienia-komentarza/
    Description: Wtyka dodaje ikony socjali do każdego postu w głównym widoku, oraz jednorazowo wyświetla zachętę-info do pozostawienia komentarza
    Version: 1.0
    Author: WikS.eu
    Author URI: http://wiks.eu
    License: GPLv3
    License URI: http://www.gnu.org/licenses/gpl-3.0.html
 */

treść nagłówka tworzy opis wtyczki:

 

Foldery i pliki pluginu w WordPressie:

 

…co dalej w PHP?

class WiksPopup {
    
//    public $options;    
    
    /** treść dodawana przed końcem strony
     * 
     */
    public function wikseu_popup_content() {

        $dir_img = plugins_url( 'img/', __FILE__ );
        $dir_js = plugins_url( 'js/', __FILE__ );
        $dir_css = plugins_url( 'css/', __FILE__ );

        $wikseu_popup_dev_mode = true; // tryb roboczy, m.in nie reaguje na zapisane cookie

Tworzę klasę WiksPopup, definiuje adresy katalogów umieszczonych we wtyczce względem __FILE__ oraz -na potrzeby produkcyjne ustawiam dev = true.

…komentarz mówi o treści dodawanej przed końcem strony. WordPress oferuje system tzw. zaczepów – hooks, które umożliwiają wykonywanie fragmentów swojego kodu w zdefiniowanych okolicznościach. Nas interesuje koniec strony, znaleziony zaczep to ‚wp_footer’, zaś sposób dodania kodu do niego to:

add_action( 'wp_footer', array('WiksPopup', 'wikseu_popup_content') );

wewnątrz add_action mamy nazwę zaczepu: ‚wp_footer’ oraz tablicę z dwoma elementami – nazwą utworzonej klasy i metody.

Znaną wcześniej treść HTML wstawiamy jako PHP:

echo '
<!-- ======================== WikS.eu -Plum ===================================== -->
<div id="wikseu_popup"> <!-- div, który zniszczymy po wszystkim -->
   <div id="wikseu_popup_outer">
       <div id="wikseu_popup_middle">
           <div id="wikseu_popup_inner">
               <div id="wikseu_popup_popup"> <!-- div po którym biegają ikonki -->
[...]

                <!-- socjal ikonki -->';
 foreach ($options['we_checkbox_x'] as $one_file_ico ) {
     echo '<img class="wikseu_popup_icon" src="'.$dir_img.'/'.$one_file_ico.'">';
 }
 echo '     </div>

[...]

<script>';
echo "var wikseu_popup_is_admin = ";
if ( ! is_admin() ) {
    echo "true;";
}else{
    echo "false;";
}
echo "var wikseu_popup_dev_mode = "; // czy tryb roboczy
if ($wikseu_popup_dev_mode === true) {
    echo "true;"; // tryb roboczy, m.in nie reaguje na zapisane cookie    
}else{
    echo "false;"; // tryb normalny
}

[...]

wp_enqueue_script( 'wikseu_popup', $dir_js . 'wikseu_popup.js', array('jquery'), null, true);
wp_enqueue_style( 'wikseu_popup', $dir_css . 'wikseu_popup.css' );


…fragment w którym wrzucaliśmy ikonki socjal mediów zmieniamy na pokazany wyżej -pozwoli to na lepsze operowanie ikonami – ich nazwy będą pobierane z utworzonej tablicy, zaś ta będzie definiowana w ustawieniach…

Będziemy ponadto sprawdzać, czy zalogowany użytkownik jest adminem, a także przenosić informację o trybie DEV/produkcyjnym z PHP do JavaScript.

Ostatni fragment to dodanie skryptu oraz arkusza stylów w akceptowalny przez WordPress sposób, przy skrypcie zaznaczamy, że wymaga ‚jQuery’ do działania.

Właściwie jeszcze jedną niezbędną w tym wypadku koniecznością okazała się zmiana wszystkich jQuerowych prefixów ‚$’ na pełne ‚jquery’ w kodzie. Taka uroda WordPressa.

 

dalej…

Zdarza się, że używając WordPressa (a także każdego innego narzędzia do budowy strony) dokonujemy czasem zmian, które nie są widoczne na pierwszy oka rzut (a chcielibyśmy powiadomić o nich użytkowników). W moim przypadku było tak z uruchomieniem na jednej ze stron systemu komentarzy Disqus, Facebook, Google+ -obok istniejącego WordPress’owego.

Pomyślałem, że dobrym sposobem będzie wykonanie okienka informującego o tym.

No i nie chodziło mi o

alert("Patrzcie wszyscy!");

Opiszę budowę pluginu do WordPressa, wraz ze stroną ustawień.

Zapraszam do lektury.

Zabawę zacząłem od Netbeans i utworzenia projekty HTML+CSS+JS, który następnie przeniosłem do WP.

Idea była taka, aby oprócz okienka z treścią słowno obrazkową pokazały się również ikonki mediów społecznościowych, …no aby nie były całkiem statyczne – raczej figlarne, aby okienko nie utrudniało zbytnio życia (czyli pokazało się raz danemu użytkownikowi, wzbudzając raczej niedopowiedzenie i ciekawość niż przesyt).

Zobaczcie tutaj, czyste HTML+CSS+JS (no dobra, trochę jQuery też).

fragmenty:

        <!-- ======================== WikS.eu -Plum ===================================== -->
        <div id="wikseu_plum" style="display: none;"> <!-- div-outer, który zniszczymy po wszystkim -->
            <div id="wikseu_plum_outer">  <!-- div, który pomoże w wyśrodkowaniu -->
                <div id="wikseu_plum_middle">  <!-- div, który pomoże w wyśrodkowaniu -->
                    <div id="wikseu_plum_inner">  <!-- div, który pomoże w wyśrodkowaniu -->
                        <div id="wikseu_plum_popup"> <!-- div po którym biegają ikonki -->
                            <table id="wikseu_plum_table"> 
                                <tr>
                                    <td style="width: 75px;">
                                        <img src="img/lupa_wiatrak_onl4.gif">
                                    </td>
                                    <td id="wikseu_plum_td">
                                        <h4>Hey, miło Cię widzieć !<br>Pozwól -zabiorę chwilkę i tylko raz</h4>
                                    </td>
                                </tr>
                                <tr>
                                    <td colspan="2">
                        <button id="wikseu_plum_close" style="display: none;">ok</button>
                                    </td>
                                </tr>
                            </table>
                        </div> <!-- div po którym biegają ikonki -->
                        <!-- socjal ikonki -->
                        <img class="wikseu_plum_icon" src="img/discus36.png">
                        <img class="wikseu_plum_icon" src="img/faceb36.png">
                        <img class="wikseu_plum_icon" src="img/goog36.png">
                        <!-- <img class="wikseu_plum_icon" src="img/twi36.png"> -->
                        <img class="wikseu_plum_icon" src="img/wp36.png">
                    </div> <!-- div, który pomoże w wyśrodkowaniu -->
                </div> <!-- div, który pomoże w wyśrodkowaniu -->
            </div> <!-- div, który pomoże w wyśrodkowaniu -->
        </div> <!-- div, który zniszczymy po wszystkim -->
        <script src="js/wikseu_plum.js"></script> 
        <!-- ======================== WikS.eu -Plum ===================================== -->

czyli kolka DIV do wyśrodkowania wszystkiego w pionie i poziomie, a także do zniszczenia po wszystkim, w środku prosta tabelka z obrazkiem GIF (dzięki czemu może być ruchomy – w tym przypadku wiatrak), treść, przycisk OK i ikony socjal-mediów.

tutaj można zobaczyć jak to wygląda bez CSS i JavaScript

.. CSS: – głównie wyrównanie i kolory
i JavaScript…
…która musi wszystko zdynamizować, a mianowicie poruszyć ikony tak jak sobie to wyobraziłem. Wędrują z krawędzi ekranu, później w okolicy okienka są aktywne, na koniec toczą się jak kule bilardowe a po chwili wypadają na boki poddając się grawitacji.
No i jeszcze trzeba zapamiętać w ciasteczku cookie, że już się prezentowaliśmy…

JavaScript będzie w kawałkach:

// gdy załaduje się strona:
$(document).ready(function(){
    // pobierz Cookie -gdy jest:
    var plum_comment = getCookie("plum_comment");
    // sprawdź, czy pobrano lub czy to tryb developerski:
    if ( !plum_comment || dev_mode === true ) {
        // pokaż DIVa -plum
        var div = document.getElementById('wikseu_plum');
        div.style.display = 'table';

…czyli startujemy po załadowaniu dokumentu, pobieramy Cookie aby wiedzieć czy już się prezentowaliśmy, jeśli nie lub jeśli jest to tryb dev to startujemy z okienkiem – do tej pory było ukryte, teraz je pokazujemy..

// ustal położenie startowe ikonek
icon_l = document.getElementsByClassName("wikseu_plum_icon");
for (var icon_index = 0; icon_index < icon_l.length; icon_index++) {
    icons[icon_index] = [
        icon_l[icon_index], 
        - oico_warea/2, // top
        (icon_l.length - 1 - icon_index) * oico_warea, // left
        0  // angle-rotate
    ];
    // id="plum0"
    icons[icon_index][0].id = 'plum' + icon_index;
    icons[icon_index][0].style.top = icons[icon_index][1] + 'px';
    icons[icon_index][0].style.left = icons[icon_index][2] + 'px';
}        
// pobierz rozmiary diva:
var div_ramka_el = document.getElementById("wikseu_plum_popup");
div_ramka = [
    div_ramka_el,
    parseInt(div_ramka_el.offsetTop),
    parseInt(div_ramka_el.offsetLeft),
    parseInt(div_ramka_el.offsetTop + div_ramka_el.offsetHeight),
    parseInt(div_ramka_el.offsetLeft + div_ramka_el.offsetWidth)
];

… ustalamy położenie ikonek socjal-mediów na krawędzi zewnętrznego DIV, budujemy tablicę z obiektów ikon i ich parametrów -dzięki czemu łatwiej będzie nam wykonywać obliczenia na współrzędnych ich położenia,
pobieramy także rozmiary i położenie okienka na komputerze użytkownika, aby wiedzieć gdzie ikonki mają się poruszać.

// ukaż treść wnętrza diva:
$("#wikseu_plum_popup").fadeIn(1000);
$(".wikseu_plum_icon").each(function(index) {
    $(this).delay(400*index).fadeIn(1000);
}).each(function(index) {
    icons[index][1] = div_ramka[1] + 1; // Top
    $(this).animate({
        top: icons[index][1] + 'px',
        speed: 'slow'
    },
    function() {
        parolle(index);
    });
});
reload_popup_content();

…ukazujemy treść, kolejno ikonki z odstępem 0,4sekundy, uruchamiamy procesy toczenia się w kierunku górnej krawędzi okienka (parolle) oraz przeładowania treści tekstowej okna (reload_popup_content)
Przy odczycie i zapisie cookie używałem funkcji opisanych tutaj: https://www.w3schools.com/js/js_cookies.asp

Proces ikonkowy „parolle(index)”:

** start obrotu ikonkami
 * 
 * @param {type} index
 * @returns {undefined}
 */
function parolle(index) {
    
    var step = 1 + 0.5 * index;
    mid_arolle(index, step);
}

/** środek funkcji obrotu ikonkami
 * 
 * @param {type} index
 * @param {type} step
 * @param {type} angle
 * @returns {undefined}
 */
function mid_arolle(index, step=0, angle=0) {

    icons[index][3] = angle; // zapisz kąt i ustaw:
    icons[index][0].style.webkitTransform = "rotate(" + icons[index][3] + "deg)";
    // zapisz położenie-left i ustaw:
    // przed prawą krawędzią ramki:
    icons[index][2] = div_ramka[4] - 360 + angle - (oico_warea * (1 + index));
    icons[index][0].style.left = icons[index][2] + 'px';
    // wykonaj kroczek obrotu:
    if (!break_actions) {
        if (angle < 360) {
            angle += step;
            setTimeout(function() {
                mid_arolle(index, step, angle);
            }, 2);        
        }else{
            shake(index, angle, time_shakesum_mslike, spadaj2);
        }
    }
}

to w istocie dwie funkcje – startująca proces (parolle) oraz kontynująca go (mid_arolle)
kontynuacja zaś to kolejne kroki przesunięcia ( style.left = ) oraz obrotu ( style.webkitTransform = „rotate(” )
I na koniec uruchomienie następnego procesu, nazwanego ‚shake’
W wyobrażeniu to tak: ikonki pojawiły się, przetoczyły na swoje miejsca (kolejność) po prawej stronie górnej krawędzi okienka i teraz shake – zaczną się trząść…

/** drgaj ikonami przez określony czas
 * 
 * @param {type} index
 * @param {type} angle
 * @param {type} timerest
 * @param {type} mfunk
 * @returns {undefined}
 */
function shake(index, angle, timerest, mfunk=null) {

    var deltatime_shake = 3; // szybkość drgań ikonek
    // drgnij:
    icons[index][0].style.top = (icons[index][1] - 1 + 2 * Math.random()) + 'px';
    icons[index][0].style.left = (icons[index][2] - 1 + 2 * Math.random()) + 'px';
    icons[index][0].style.webkitTransform = "rotate(" + (icons[index][3] 
            + 20 * Math.random() - 10) + "deg)";
    // ile zostało jeszcze szasu drgań:
    timerest = timerest - deltatime_shake;
    // ...i raz jeszcze:
    if (!break_actions) {
        if (timerest > 0) {
            setTimeout(function() {
                shake(index, angle, timerest, mfunk);
            }, deltatime_shake);
        }else{
            // odpal funkcję następną, gdy jest
            if(mfunk) {
                // spadanko na dolna krawedz
                mfunk(index);
            }
        }
    }
}

Drgania ikonek -losowo wzdłuż osi X i Y +/-1px oraz obrót wokół oso +/-10stopni. Całość przez określony czas, później wywołanie kolejnej funkcji – spadania na dolną krawędź

/** przemieszcza ikonki w dół
 * - spadają nad dolną krawędź diva
 * @param {type} index
 * @param {type} mfunk
 * @returns {undefined}
 */
function spadaj2(index, mfunk=null) {
    
    icons[index][1] = div_ramka[3] - 43;
    $(icons[index][0]).animate({
        top: icons[index][1] + 'px',
        speed: 'slow'
        }, function() {
            // odpal funkcję następną, gdy jest
            wolno_tocz_sie(index);
        }
    );
}

Po spadku, kolejna funkcja – wolno_tocz_sie(index). Funkcja ta powoduje przesuwanie i obracanie wokół osi ikonek, sprawiając wrażenie toczenia się po dolnej krawędzi okienka, od lewej do prawej strony -z odbijaniem, przy losowo ustalonej prędkości dla każdej z ikon.

/** dalej toczą się indywidualnie od lewej do prawej
 * 
 * @param {type} index
 * @param {type} speed
 * @param {type} gravitation
 * @returns {undefined}
 */
function wolno_tocz_sie(index, speed=null, gravitation=0) {

    var delay_rollover = 5;
    if (speed === null) {
        speed = 0.5 + 2 * Math.random();
    }
    if (!break_actions) {
        // jeśli przekroczyłby granice ramki:
        if (icons[index][2] < div_ramka[2] 
                || icons[index][2] > div_ramka[4] - oico_warea) {
            // i są te granice:
            if (!no_border) {
                speed = -speed;
[...]
            }                
        }
        // oblicz i ustaw położenie-left:
        icons[index][2] = icons[index][2] - speed;
        icons[index][0].style.left = (icons[index][2]) + 'px';
        // oblicz i ustaw kąt obrotu:
        icons[index][3] = icons[index][3] - speed * 2;
        icons[index][0].style.webkitTransform = "rotate(" + icons[index][3] + "deg)";
        // ...i tak dalej:
        setTimeout(function() {
            wolno_tocz_sie(index, speed, gravitation);
        }, delay_rollover);
    }
}

…tak więc odbijają się z lewa na prawo do momentu aż …?
Aż:

 no_border == true

Czyli do momentu aż znikną granice, co wtedy? (to jest właśnie w miejscu trzykropka, zobaczmy:)

// ach, tu już jest Schengen!
// ustaw Cookie:
setCookie("plum_comment", '1', 365);
// porusz się także w dół (już nie zapamiętuj -olać):
icons[index][0].style.top = (icons[index][1] + gravitation) + 'px';
// ustaw przyspieszenie i prędkość opadania
var acceleration = 0.05;
if (gravitation === 0) {
    gravitation = 1;
}
gravitation = gravitation * (1 + acceleration);
// czy odleciał już sporo?
if(gravitation > 100) {
    // tak, odleciał, ale...
    if (gravitation < 150) {
        // ale nie tak bardzo daleko, -zaniknij stopniowo:
        icons[index][0].style.opacity = 1 - (gravitation-100)/50;
    }else{
        // już bardzo daleko odleciał - zaniknij całkowicie:
        icons[index][0].style.display = 'none';
        icons[index][1] = null; // zapamiętaj że go już nie ma
        // sprawdź, czy wszystkie zaniknęły:
        var something_exist = false;
        for(var i = 0; i < icon_l.length; i++) {
            if(icons[i][1] !== null) { // to co zapamiętałeś powyżej
                something_exist = true; // nie, coś jeszcze nie zanikło
            }
        }
        // czy wszystkie już zaniknięte?
        if (!something_exist) {
            // tak, wszystkie -zaniknij teraz okno właściwe
            $('#wikseu_plum').fadeOut('fast', 
                // gdy okno zaniknie, usuń element DIV-plum
                function(){    
                    var elem = document.getElementById('wikseu_plum');
                    elem.parentNode.removeChild(elem);                                
                }
                        );
        }
    }
}

…czytam komentarze w kodzie i właściwie wszystko w nich opisałem 🙂
Po zniknięciu wszystkich ikonek zniszcz głównego DIV, aby nie zaśmiecał treści…

Pozostał jeszcze do opisania proces przeładowania treści okna:

/** przeładowanie zawartości HTML diva okienka plum
 * 
 * @returns {undefined}
 */
function reload_popup_content() {
    // element, do którego należy wrzucać treść:
    var content_el = document.getElementById('wikseu_plum_td');
    // czy wrzucano już coś?
    if (popup_text_showed_id >= 0) {
        if (popup_text_showed_id < popup_text_list.length) {
            // cykl: fadeOut --> reload --> fadeIn
            // ukryj dotychczasową treść, 
            $("#wikseu_plum_td").fadeOut('fast', function() {
                // gdy ukryta --> wrzuć nową treść:
                content_el.innerHTML = popup_text_list[popup_text_showed_id];
                // i ukaż
                $("#wikseu_plum_td").fadeIn('slow');
                // cyknij ID dla następnej treści:
                popup_text_showed_id = popup_text_showed_id + 1;
            });
        }else{
            // po ostatniej ukaż przycisk 
            // i ew ustaw timeout dla samoczynnego jego przyciśnięcia
            $('#wikseu_plum_close').click(function(){
                no_border = true;
                return; // bo gdybyprzycisk był wcześniej to nie kontynuj pokazu
            });
            // samoczynne Schengen po 5 sekundach
            setTimeout(function() {
                no_border = true;
            }, time_self_schengen_ms);
        }
    }else{
        // jeszcze nic nie wrzucano:
//        $("#wikseu_plum_popup").fadeIn(500);
        popup_text_showed_id = 0;
    }
    /// ...i znów -po czasie pewnym
    setTimeout(function() {
        reload_popup_content();
    }, time_one_reloaded_ms);
}

…i też komentarze zawierają sporo…

o wyśrodkowaniu wszystkiego CSS można przeczytać tutaj

zaś całość prezentuje się tak

Ok, pora teraz przenieść to do WordPressa, budując plugin i jego ustawienia, z myślą aby można go wykorzystać w przyszłości także do innych zabaw.

dalej…

Czym jest? Najkrócej – pośrednikiem w nawiązaniu połączenia internetowego.

Czym może być? -Pewnego rodzaju parawanem, za którym możemy próbować ukryć się przed tym, kogo oglądamy. Wikipedia, także tutaj.

Gdy odwiedzamy stronę internetową (lub robi to np. pracujący dla nas program), przedstawiamy się i przesyłamy dane o sobie …przeglądarce, urządzeniu, adresie IP etc. Pewne z nich możemy zmyślić, zakłamać, zataić, niektórych zaś, zwyczajnie się nie da.

Serwer proxy może wykonać za nas pewne operacje, przedstawiając się swoim adresem i danymi – zamiast naszych. Taka jest w skrócie idea naszego parawanu.

Przesyłamy informację do proxy, on wysyła je do serwera do którego chcemy dotrzeć, zaś odebraną informację zwraca nam. Jest to usługa, można za nią zapłacić, można też spróbować skorzystać z oferty darmowych serwerów proxy, których jest niezliczona ilość… wystarczy zapytać wujka Google, np tutaj: https://free-proxy-list.net/

Możemy także sprawdzić jak działają, czy zakrywają nas ‚od stóp do głowy’ -vide parawan, czy też coś wystaje…

Zaczynając od początku, albo też od strony serwera do której trafiają zapytania z proxy, napisałem krótki PHPowy skrypcik -stronę, która wyświetla to, co strona otrzyma od naszej przeglądarki gdy ją otworzymy.

Zobaczmy tutaj

środek skryptu wygląda tak:

https://github.com/wiks/proxy/blob/master/php/index.php

to co możemy nakłamać sami to HTTP_USER_AGENT – rodzaj systemu, przeglądarka etc, to zaś, co chcemy ukryć to nasz adres IP…

Jak przetestujemy darmowe proxy? Będziemy każdemu z nich zadawać adres naszego skryptu PHP, zaś proxy powinno wysłać do niego zapytanie i to co otrzyma – zwróci, tak więc sprawdzimy, czy w tym co wróciło nie ma naszego adresu IP.

Wykorzystamy także tabele w bazie danych do przechowywania wyniku i kilku ustawień. Głównym narzędziem odpytującym listę proxy będzie Python:

https://github.com/wiks/proxy/blob/master/python/__init__.py

Pośród sprawdzonych 600 darmowych proxy, 94 zwróciły satysfakcjonujący wynik, nie zawierający źródłowego IP …w pozostałych z za ‚parawanu’ widać albo buty i skarpetki, albo połyskującą łysinę… niedobrze.

 

Po uruchomieniu program ostrzega/przypomina, aby uzupełnić dane, tj: jakieś nagłówki user-agent którymi będzie przedstawiał się serwerowi proxy (tak, nakłamie trochę) – jeśli nie podamy będzie używał ‚Mozilla/5.0’. Program potrzebuje także adresu, który zleci proxy do wywołania (u mnie jest to „http://www.wiks.eu/ip” i -jeśli go nie zablokuję to może być ten).

Następnie adres IP, którego będzie wyszukiwał w treści zwróconej przez proxy, jeśli grak to próbuje go ustalić za pomocą „http://www.wiks.eu/ip/myip.php” (też zastrzegam sobie prawo zablokowania).

Log początku programu:

WARNING put some user agents into "webbrowser_headers" table :-)
WARNING set URL for testing content returned by proxy server, e.q. like "http://www.wiks.eu/ip" :-)
WARNING put some proxy`s data IP:port into table "all" columns: "ipaddress" & "port" to check it :-)

…trzecie ostrzeżenie to oczywiście prośba o wpisanie do bazy adresów serwerów proxy do tesów (użyłem tych z: https://free-proxy-list.net/ )

Radości!

WikS.eu