Aby wykorzystać w WordPressie dane z serwera pogodowego, pobierane dla tokena, należy napisać widget.

Posłużę się tutaj prostym przykładem, który oczywiście można dowolnie przerobić.
Przy pisaniu widgetu korzystałem z opisów na stronie: https://wpadmin.pl, tak więc po szczegóły dot budowy w części WordPressowej sugeruję zerknąć na WPadmin.

Budowany widget będzie miał dwie klasy (pobierania danych i senso-stricte widgetowy) i prosty panel ustawień, informacja prezentowana będzie w formie tekstowej (ale będą zbudowane podstawy do prezentowania jej także w formie graficznej, albo wręcz wskazań poszczególnych parametrów aktualnej temperatury), będzie także prosty panel ustawień sposobów wyświetlania.

Klasa pobierająca dane

 

W zależności od miejsca dla którego czytamy dane, zdefiniowanego posiadanym tokenem, uzyskamy dane pogodowe aktualne i prognozę (ok. 60 miejsc – głównie w Polsce) lub tylko prognozę (ok. 300 miejsc w Polsce).
Klasa pobierająca dane ma za zadanie pobrać, sprawdzić i ujednolicić je w ten sposób, aby w łatwy sposób przedstawić w widgecie.

przykładowe dane z serwera- pogoda i prognoza:

"{"w":[{"name":"\u015awinouj\u015bcie","dt":"2018-07-18 09:00:00",
         "descr_forecast":"w godz.20-... bezchmurne niebo, temp. +20...+21\u00b0C, \u0142agodny...umiarkowany wiatr (3-4B) z kierunku NW, ci\u015bnienie 1027 hPa, rozchmurzy si\u0119 do 0\/8 , wilgotno\u015b\u0107 84...91 %",
         "descr_now":"pochmurnie, temperatura +19\u00b0C, wiatr z kierunku NW 3B (ok.5m\/s), ci\u015bnienie 1011hPa, wilgotno\u015b\u0107 93% ",
         "urln":"http:\/\/pogoda.wiks.eu\/a\/18092323_GWIBC9F5.wav",
         "urlf":"http:\/\/pogoda.wiks.eu\/a\/18092323_NMIPGAX9.wav",
         "img_forecast":"http:\/\/pbs.twimg.com\/media\/DiXuBp7XcAUVk2-.png",
         "lat":"53.923","lng":"14.278",
         "windspeed":"5",
         "winddir":"320",
         "humidity":"93",
         "pressure":"1011","clouds":"75","temp":"19"}],
          
         "f":[{"name":"\u015awinouj\u015bcie","dt":"2018-07-18 09:38:58",
         "for_date":"2018-07-18",
         "forec_descr":"w godz....-16 i 20-... przelotny deszcz, temp. +18...+19\u00b0C, \u0142agodny...umiarkowany wiatr (3-4B) z kierunku N, ci\u015bnienie 1028 hPa, zachmurzenie 7\/8, wilgotno\u015b\u0107 95...100 %",
         "url":"http:\/\/pogoda.wiks.eu\/a\/18093858_BM5GPWT2.wav"},
         {"name":"\u015awinouj\u015bcie","dt":"2018-07-18 09:38:59",
         "for_date":"2018-07-19",
         "forec_descr":"w godz....-0 i 8-13 przelotny deszcz, w godz.2-7 umiarkowany deszcz, w godz.20-... bezchmurne niebo, temp. +19...+21\u00b0C, umiarkowany...do\u015b\u0107 silny wiatr (4-5B), z kierunk\u00f3w NW, N, ci\u015bnienie 1027...1029 hPa, zachmurzenie 0-8\/8, wilgotno\u015b\u0107 87...100 %",
         "url":"http:\/\/pogoda.wiks.eu\/a\/18093858_VR7SIGAO.wav"},
         {"name":"\u015awinouj\u015bcie","dt":"2018-07-18 09:38:59",
         "for_date":"2018-07-20",
         "forec_descr":"w godz....-13 bezchmurne niebo, temp. +18...+20\u00b0C, powiew...umiarkowany wiatr (1-4B), z kierunku NW, ci\u015bnienie 1027...1029 hPa, zachmurzenie 0-3\/8, wilgotno\u015b\u0107 86...96 %",
         "url":"http:\/\/pogoda.wiks.eu\/a\/18093859_ZWRU2J24.wav"},
         {"name":"\u015awinouj\u015bcie","dt":"2018-07-18 09:38:59",
         "for_date":"2018-07-21",
         "forec_descr":"w godz.2-16 bezchmurne niebo, temp. +18...+24\u00b0C, powiew...umiarkowany wiatr (1-4B), z kierunk\u00f3w E, SW, ci\u015bnienie 1021...1027 hPa, zachmurzenie 0-7\/8, wilgotno\u015b\u0107 74...99 %",
         "url":"http:\/\/pogoda.wiks.eu\/a\/18093859_L2J2U4W5.wav"}]}"

przykładowe dane z serwera- tylko prognoza:

          
"{"p":{
         "0":{"name":"Krosno Odrza\u0144skie","dt":"2018-07-17 19:30:51","for_date":"2018-07-18",
         "forec_descr":"w godz....-0 i 20-... bezchmurne niebo, temp. +17...+26\u00b0C, \u0142agodny...umiarkowany wiatr (3-4B), z kierunk\u00f3w NW, N, ci\u015bnienie 1016...1019 hPa, zachmurzenie 0-7\/8, wilgotno\u015b\u0107 50...80 %",
         "url":"http:\/\/pogoda.wiks.eu\/a\/17193051_LRQRZM4I.wav"},
         "1":{"name":"Krosno Odrza\u0144skie","dt":"2018-07-17 19:30:52","for_date":"2018-07-19",
         "forec_descr":"w godz....-0, 5-10, 14-... bezchmurne niebo, temp. +16...+26\u00b0C, \u0142agodny wiatr ( 3B ), z kierunku NW, ci\u015bnienie 1018...1020 hPa, zachmurzenie 0-6\/8, wilgotno\u015b\u0107 38...70 %",
         "url":"http:\/\/pogoda.wiks.eu\/a\/17193052_6K0U092W.wav"},
         "name":"Lulla"}}"         

Wynik działania klasy pobierająco przetwarzającej jest podobny do:

Array
(
    [type] => w
    [name] => Świnoujście
    [w] => Array
        (
            [dt] => 2018-07-18 11:30:00
            [descr] => przelotny deszcz, temperatura +19°C, wiatr z kierunku NW 3B (ok.4m/s), ciśnienie 1013hPa, wilgotność 88% 
            [descr_12h] => w godz....-16 i 20-... przelotny deszcz, temp. +18...+19°C, łagodny...umiarkowany wiatr (3-4B) z kierunku N, ciśnienie 1028 hPa, zachmurzenie 7/8, wilgotność 95...100 %
            [img] => 
            [url] => http://pogoda.wiks.eu/a/18115504_I4SCADDS.wav
            [url_12h] => http://pogoda.wiks.eu/a/18115504_ZLGTPN2S.wav
        )
    [f] => Array
        (
            [2018-07-18] => Array
                (
                    [descr] => w godz....-16 i 20-... przelotny deszcz, temp. +18...+19°C, łagodny...umiarkowany wiatr (3-4B) z kierunku N, ciśnienie 1028 hPa, zachmurzenie 7/8, wilgotność 95...100 %
                    [url] => http://pogoda.wiks.eu/a/18093858_BM5GPWT2.wav
                )
            [2018-07-19] => Array
                (
                    [descr] => w godz....-0 i 8-13 przelotny deszcz, w godz.2-7 umiarkowany deszcz, w godz.20-... bezchmurne niebo, temp. +19...+21°C, umiarkowany...dość silny wiatr (4-5B), z kierunków NW, N, ciśnienie 1027...1029 hPa, zachmurzenie 0-8/8, wilgotność 87...100 %
                    [url] => http://pogoda.wiks.eu/a/18093858_VR7SIGAO.wav
                )
            [2018-07-20] => Array
                (
                    [descr] => w godz....-13 bezchmurne niebo, temp. +18...+20°C, powiew...umiarkowany wiatr (1-4B), z kierunku NW, ciśnienie 1027...1029 hPa, zachmurzenie 0-3/8, wilgotność 86...96 %
                    [url] => http://pogoda.wiks.eu/a/18093859_ZWRU2J24.wav
                )
            [2018-07-21] => Array
                (
                    [descr] => w godz.2-16 bezchmurne niebo, temp. +18...+24°C, powiew...umiarkowany wiatr (1-4B), z kierunków E, SW, ciśnienie 1021...1027 hPa, zachmurzenie 0-7/8, wilgotność 74...99 %
                    [url] => http://pogoda.wiks.eu/a/18093859_L2J2U4W5.wav
                )
        )
    [dt] => 2018-Jul-18 09:38:59
)
-------------- lub ----------------
Array
(
    [type] => p
    [name] => Lulla
    [f] => Array
        (
            [2018-07-18] => Array
                (
                    [descr] => w godz....-0 i 20-... bezchmurne niebo, temp. +17...+26°C, łagodny...umiarkowany wiatr (3-4B), z kierunków NW, N, ciśnienie 1016...1019 hPa, zachmurzenie 0-7/8, wilgotność 50...80 %
                    [url] => http://pogoda.wiks.eu/a/17193051_LRQRZM4I.wav
                )
            [2018-07-19] => Array
                (
                    [descr] => w godz....-0, 5-10, 14-... bezchmurne niebo, temp. +16...+26°C, łagodny wiatr ( 3B ), z kierunku NW, ciśnienie 1018...1020 hPa, zachmurzenie 0-6/8, wilgotność 38...70 %
                    [url] => http://pogoda.wiks.eu/a/17193052_6K0U092W.wav
                )
        )

    [dt] => 2018-Jul-17 19:30:52
)

Klasa widgetu – zobrazowanie

Przetworzenie powyższego dla zobrazowania:

/** Wyświetlanie widgetu uzytkownikowi
 * 
 * @param type $args
 * @param type $instance
 */
public function widget($args, $instance) {

   $my_html_content = '';
   $g = new GETpogodaWiksEu();
   $vcontent = $g->get_and_validate_for_token($instance['token']);
   if($vcontent) {
      if(!empty($vcontent['name']) && !empty($vcontent['f'])) {
         $my_html_content = '<h3>'.$vcontent['name'].'</h3>';
         $lp_days = '0';
         foreach($vcontent['f'] as $key=>$value) {
            if($lp_days++ < $instance['max_days']) {
               $my_html_content .= '<h4>';
               if(!$instance['days_as_date_only'] === true) {
                  $inside_key = $key;
               }else{
                  $inside_key = $this->prittyfy_dt($key);
               }
               if($instance['allow_audio'] === true && !empty($value['url'])){
                  $img = '<img src="'.$this->url_img.'speaker50.gif'.'" 
                  style="width:20px;height:20px;">';
                  $my_html_content .= '<a href="'.$value['url'].'">'.$img.' '.$inside_key.'</a>';
               }else{
                  $my_html_content .= $inside_key;
               }
               $my_html_content .= ':</h4>';
               $my_html_content .= $value['descr'].'<br>';
            }
         }
      }
   }
   echo '<div class="widget" id="wiks_weather_content">'.$my_html_content.'</div>';
}

Zaś opcje:

/**
 * Outputs the options form on admin
 *
 * @param array $instance The widget options
 */
public function form( $instance ) {

   //ustawiamy opcje domyslne
   $my_defaults = array(
      'token' => 'for_test_cntcXBWYJDQ0H6VwanN3Zrh', // test token
      'max_days' => 3,
      'days_as_date_only' => false,
      'allow_audio' => true,
   );
   $instance = wp_parse_args( (array) $instance, $my_defaults );
   ?>
   <p>
   <!-- token -->
   <label for="<?php echo $this->get_field_id('token'); ?>"><?php _e('Token:'); ?></label>
   <input class="widefat" id="<?php echo $this->get_field_id('token'); ?>" name="<?php echo $this->get_field_name('token'); ?>" type="text" value="<?php echo esc_attr($instance['token']); ?>" />
   </p>
   <p>
   <!-- max days show -->
   <label for="<?php echo $this->get_field_id( 'max_days' ); ?>"><?php _e( 'Maksymalna liczba dni prognozy:' ); ?></label>
   <input id="<?php echo $this->get_field_id( 'max_days' ); ?>" name="<?php echo $this->get_field_name( 'max_days' ); ?>" type="text" value="<?php echo $instance['max_days']; ?>" size="3" />
   </p>
   <p>
   <!-- czy zmieniać daty na przyjazne opisy dziś jutro -->
   <input class="checkbox" type="checkbox" name="<?php echo $this->get_field_name('days_as_date_only'); ?>" id="<?php echo $this->get_field_id('days_as_date_only'); ?>" value="true" <?php checked(true, $instance['days_as_date_only']);?> />
   <label for="<?php echo $this->get_field_id('days_as_date_only'); ?>"> <?php _e( 'pokaż przyjazne opisy dni np. "dziś" zamiast "'.date('Y-m-d', time()).'"' ); ?></label><br />
   </p>
   <p>
   <!-- czy tworzyć linki do audio -->
   <input class="checkbox" type="checkbox" name="<?php echo $this->get_field_name('allow_audio'); ?>" id="<?php echo $this-
   >get_field_id('allow_audio'); ?>" value="true" <?php checked(true, $instance['allow_audio']);?> />
   <label for="<?php echo $this->get_field_id('allow_audio'); ?>"> <?php _e( 'utwórz odnośniki do audio' ); ?></label><br />
   </p>
   <?php
}

opcje prezentują się tak:

Widget zaś jest tutaj:


 

Radości!
WikS

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?

Ciekawostka, którą potrzebowałem niedawno wykorzystać.

Uruchomienie skryptu Pythona poprzez PHP, czyli np. uruchomienie strony www.

Serwer pracujący pod Apache2, PHP chyba 5.6 (zresztą to nie ma chyba większego znaczenia), Python 2.7 w tym wypadku.

w PHP wygląda to tak:

<?php 
echo '...'.date("Y-m-d H:i:s").'<br>';
$only_path = '/home/wiks/Dokumenty/projects/weather/python2/php_fired.py'; $command = escapeshellcmd($only_path); $output = shell_exec($command); echo $output; echo '<br>'.date("Y-m-d H:i:s").' <-- już :-) ';

* jeśli miast shell_exec() byłoby exec(), otrzymalibyśmy na wyjściu tylko ostatnią wyprintowaną linijkę, a chcielibyśmy przecież całość…

zaś w Pythonie: php_fired.py

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

[...]

# output poprzez zwykłe:
print('coś tam')  # to działa, ale:
# print(u'coś tam') # powoduje błąd, więc trzeba tak:
print(u'coś tam'.encode('utf-8'))  # to działa

# ja zaś potrzebowałem zamiany tekstu na mowę, i po kilku próbach optymalne jest takie rozwiązanie:
# - przy czym tekst wejściowy jest UNICODE

c = 'espeak -v pl -w ' + fpath + '/' + fname + ' -k5 "%s" ' % text2spaech
execute_unix(c.encode('utf-8'))

W powyższym przypadku tekst z PRINT jest drukowany w przeglądarce.

Oczywiście Python musi być wykonywalny (chmod +x php_fired.py) – w moim wypadku przez www-data, bardzo istotna jest pierwsza linijka w skrypcie Pythona:
#!/usr/bin/env python jeśli używamy virtual-env to czasem nie jest ona potrzebna, w tym wypadku jednak tak.

Kolejnym pytaniem jak przesłać dane do PHP a później przekazać je do Pythona?

Ja testowałem zrobiłem to tak:

Python tworzy dict, zamienia go na JSON i wywołuje request.post z tymi danymi, request.post wywołuje PHP, które przekazuje dane POST/JSON do skryptu Pythona, który testowo zwraca je do PHP poprzez print, zaś PHP poprzez echo do testującego skryptu Python’a 🙂

testujący skrypt Pythona:

– tworzy dane:

post_data = {'arg1': 'tralala',
             'arg2': 123.45,
             'arg3': 'ułnicode str ąśćęłóźć ',
             'arg4': u'ułnicode str ąśćęłóźć ',
             }

i wysyła pod:

url = 'http://localhost/run_python.php'
res = requests.post(url, json.dumps(post_data))

pod powyższym adresem jest PHP, które odbiera poprzez:

$raw_data = file_get_contents( 'php://input' );
// print_r($raw_data); //{"arg1": "tralala", "arg2": 123.45, "arg3": "u\u0142nicode str \u0105\u015b\u0107\u0119\u0142\u00f3\u017a\u0107 ", "arg4": "u\u0142nicode str \u0105\u015b\u0107\u0119\u0142\u00f3\u017a\u0107 "}
$data = json_decode( $raw_data, true );
// print_r($data);
/* Array
   (
   [arg1] => tralala
   [arg2] => 123.45
   [arg3] => ułnicode str ąśćęłóźć
   [arg4] => ułnicode str ąśćęłóźć
   ) */

przetwarza dane do linijki CLI i wywołuje Pythona:

w poniższy sposób:

$cli_data = '';
foreach($data as $key => $value) {
    $cli_data .= '"'.$key.'='.$value.'" ';
}
$command = '/home/wiks/Dokumenty/projects/weather/python2/php_fired.py '.$cli_data;
// echo $command."\n";
// /home/wiks/Dokumenty/projects/weather/python2/php_fired.py "arg1=tralala" "arg2=123.45" "arg3=ułnicode str ąśćęłóźć " "arg4=ułnicode str ąśćęłóźć "
$output = shell_exec($command);
echo $output; // to jest znów zwracane do Pythona

Python:

print 'argumenty:',
for one in sys.argv:
    print one  

otrzymujemy:

argumenty: /home/wiks/Dokumenty/projects/weather/python2/php_fired.py
arg1=tralala
arg2=123.45
arg3=ułnicode str ąśćęłóźć
arg4=ułnicode str ąśćęłóźć

Trochę czasu mi zajęło dociekanie jak (ładniej) przetworzyć dane JSON Python <–> PHP <–> Python, dzięki tej dokumentacji:

http://php.net/manual/en/security.magicquotes.disabling.php

zwróciłem uwagę na przeszkadzające magic_quotes . Zaiste ‚magic’!. Po wyłączeniu – ominięciu -wszystko przestało być magiczne i stało się bardziej logiczne…

 

Gdyby chcieć to przetestować poprzez PHP, mogłoby to wyglądać tak…

źródła:
https://stackoverflow.com/questions/19735250/running-a-python-script-from-php

https://dev.to/dev_nope/catch-and-parse-json-post-data-in-php-1p56

Radości!
WikS

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…

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