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…

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…