Docker + wordpress + phpmyadmin wersja Windows

Nie opiszę tutaj wszystkiego związanego z dockerem bo jestem świerzynka w tym temacie oraz jest tego tyle że po prostu nie da się tego opisać bez przepisywania dokumentacji 😉
Będzie to tylko mały wycinek tego co jest mi potrzebne do frontentu z przykładem użycia WordPress, MySQL oraz phpMyAdmin na Windowsie.

Do czego można by użyć Dockera?

Powiedźmy że chcemy stworzyć aplikację składającą się właśnie z WordPress + MySQL + phpMyAdmin aby przygotować nowy theme czy plugin.
Wszystko to musimy zainstalować osobno na komputerze do tego PHP + apache2 lub użyć Xampp’a, ale co jeśli musielibyśmy użyć kilku wersji PHP 5.5 czy 5.6 i do tego MySQL również w kilku wersjach.

Normalnie czekało by nas bardzo długie konfigurowanie każdej instancji, odinstalowanie elementów i instalacja nowych oraz ich konfiguracja czyli jednym słowem koszmar.

W takich przypadkach pomocny jest właśnie Docker. Wystarczy wybrać kontener z wersją aplikacji i już. Można nawet uruchomić wiele kontenerów równolegle, dzięki czemu możemy testować aplikację w kilku rożnych wersjach w tym samym czasie!
Najważniejsze przynajmniej dla mnie jest to że nie zaśmiecamy sobie środowiska zbędnymi instalacjami, a jak wiemy w tym względzie Windows jest dość ułomnym środowiskiem i można go łatwo „zepsuć”.

Teraz trochę definicji.

Docker jest narzędziem , które pozwala umieścić program oraz jego zależności w przenośnym wirtualnym kontenerze. Umożliwia utworzenie kontenerów zawierających pojedyncze aplikacje ale także łączyć je w większe środowiska.

Ok zaczynamy zabawę.

Najpierw należy zainstalować Dockera na komputerze cały proces jest opisany na zerknij tutaj
W dalszej części będziemy korzystać z narzędzia Docker Compose ono jest dla wersji MAC jak i Windows jest zaszyte w instalacji Docker zaś linuxowcy muszą je osobno doinstalować.

Otwieramy konsole i wpisujemy

docker run hello-world

Jeżeli wszystko przebiegło poprawnie powinniśmy zobaczyć w konsoli to co poniżej.

Sprawdźmy jakie mamy obrazy, powinien być jeden hello-world, aby to sprawdzić piszemy

docker images

i dostajemy

Może co poważniejszego zainstalujmy na przykład nginx

docker container run -it -p 80:80 nginx

Kilka chwil i mamy uruchomioną instancję nginx, otwieramy przeglądarkę i http://localhost/index.html i mamy powitalną stronę

Teraz wyobraźcie sobie ile czasu by to zajęło w konwencjonalnym podejściu instalacja tego pod Windosem 🙂

Kilka przydatnych komend poniżej.

  • docker – wyświetla wszystkie podstawowe komendy
  • docker help – podstawowa komenda dzięki której można sprawdzić składnie oraz wyszukać inne komendy
  • docker pull [nazwaObrazu] – pobranie obrazu
  • docker search [imageName:imageTag] – komenda wyszukuje obrazy o podanej nazwie i tagu
  • docker run [args] [imageName:imageTag/imageId] – stworzenie oraz uruchomienie kontenera z obrazu
  • -it – tryb interaktywny (możemy np: korzystać z bash’a)
  • -p – protokół mapuje wystawiony port naszego kontenera na port naszego localhost
  • -d – tryb deamon, działa na zasadzie usługi
  • -expose – pozwala nam wystawić post naszego kontenera na zewnątrz
  • docker ps – komenda pokazuje nam działające kontenery jeżeli chcemy zobaczyć również te niedziałające docker ps -a
  • docker kill/stop [containerName/containerId] – komenda służy do wyłączenia działającego kontenera. Zazwyczaj korzysta się z containerId z 3 pierwszych znaków
  • docker docker rename [containerOldName] [containerNewName] – komenda służy do zmiany nazwy kontenera
  • docker rm [containerName/containerId] – usunięcie kontenera z naszego komputera o zadanej nazwie lub id
  • docker rmi [imageName:imageTag/imageId] – usuniecie z naszego komputera obrazu o podanej nazwie i tagu lub id

To tylko ułamek wszystkich flag więcej można znaleźć tutaj.

Przejdźmy do konkretów.

A mianowicie chce uruchomić tak jak na wstępie pisałem WordPress + MySQL + phpMyaAmin i swój własny theme i tutaj przyda nam się Docker Compose.
Najpierw usuńmy pozostałości po naszych poprzednich próbach. Zobaczmy co jest zainstalowane najpierw

docker images -a

później

docker rm containerId

– czyli pierwsze 3 znaki kontenera i enter.

Zaczynamy od stworzenia folderu

mkdir docker-setup

następnie

cd docker-setup

i uruchamiam visual studio code przez komendę code .

Do vsc instaluje plugin Docker poniżej


plugin pokazuję kontenery images oraz Docker Hub wystarczy kliknąć na wielorybka, trochę upraszcza nam pracę mniej pisania w konsoli 😉

Na początku tworzę w docker-setup .dockerignore praktycznie coś na styl gita

Następnie tworzę plik docker-compose.yml w nim będzie działać się cała magia 🙂

  • version – oznacza wersję Docker Compose
  • services – sekcja, w której definiujemy serwisy
  • mysql:5.7 – nazwa własna usługi
  • image – wskazujemy z jakiego obrazu ma być zbudowany kontener
  • build – wskazujemy ścieżkę do pliku Dockerfile naszego serwisu – w projekcie nie używamy
  • ports – definiujemy porty, które będą mapowane z konteneru
  • links – zależność pomiędzy kontenerami, db oraz WordPress
  • environment – zmienna środowiskowa dla danego kontenera

Aby uruchomić Docker Compose w naszym terminalu używamy komendy

docerk-compose up

w miejscu w którym mamy docker-compose.yml

Za pierwszym razem wszystkie zależności które są w yml są zaciągane – nasza baza danych jak i WordPress.
Poniżej screen z vsc oraz z chrome w którym mamy WordPress w wersji do zainstalowania 🙂

Dodajmy do tego całego naszego środowiska również phpMyAdmin

phpmyadmin:
 depends_on:
  - db
 image: phpmyadmin/phpmyadmin
 ports:
  - 8080:80
 environment:
  MYSQL_ROOT_PASSOWRD: test

Musimy najpierw zastopować wszystkie kontenery czy to przez „ctrl+c” lub z innej konsoli „docker-compose down” albo z vsc prawy przycisk na poszczególny kontener i Remove Container jak kto woli. Ja używam docker-compose down jest najpewniejszym sposobem zamknięcia uruchomionych kontenerów.

Po dodaniu phpMyAdmin uruchamiamy ponownie „docker-compose up”

Wszystko działa szybciej bo pobierany jest tylko obraz phpMyAdmin, aby sprawdzić czy działa wchodzimy na to co ostawiliśmy mianowicie http://localhost:8080
Podajemy „Użytkownika root Hasło test” tak jak podaliśmy w docker-compose.yml
I naszym oczom ukazuje się phpMyAdmin z utworzoną baza wp

Wracamy do okna http://localhost/wp-admin/install.php tam instalujemy jak zawsze i wypełniamy danymi ale ZARAZ ZARAZ!!! jeśli wyłączymy wszystkie kontenery to co dodaliśmy do WordPress a mianowicie strony, artykuły wszystko to nam zniknie!!! Nie no tak to nie możne być!

Dodajemy własne artykuły wszystko co jest nam potrzebne w WordPressa i za pomocą phpMyAdmin eksportujemy bazę do pliku db.sql (nazwa oczywiście może być inna) nie musimy tego robić możemy pobrać jakąś inną bazę danych z innego WordPress jeżeli takową posiadamy i wystarczy to przygotowania naszego theme.
Dump bazy umieszczam w folderze „data/db.sql” w naszym projekcie oraz dodaje odpowiedni wpis w docker-compose.yml

db:
 image: mysql:5.7
 volumes:
  - ./data:/docker-entrypoint-initdb.d
 restart: always
 ...

volumes pobiera nam db.sql za każdym razem gdy uruchamiamy „docker-compose up” i mamy z głowy uzupełnianie danych w bazie, możemy wejść na http://localhost i zobaczyć naszego WordPress

Dodajemy własne theme.

Teraz druga cześć chcemy dodać własny theme, ponownie stopujemy docker-compose down w głównym folderze naszego projektu dodaje folder wp-content/themes/newTemplate z plikami odpowiedzialnymi za theme.
Teraz aby to zostało zaciągnięte i widoczne w WordPressie należy dodać values tym razem do wpisu wordpress.

Ponownie docker-compose up, teraz możemy pracować na theme, aby zobaczyć zmiany które zrobiliśmy w theme wystarczy odświeżyć WordPress F5 w przeglądarce. Nie trzeba stopować kontenerów.

Źródła znajdują się pod tym adresem

Przykład użycia funkcji GROUP_CONCAT

SELECT GROUP_CONCAT(kto ORDER BY kto ASC SEPARATOR ', ') FROM raport_ludzie_spotkania

Złączenie rekordów wątku do jednego stringa, wycinanie 80 znaków stringa:

SELECT t.ticket_id, t.created,
SUBSTRING(last_notes_title, 1, (80 + LOCATE(' ', last_notes_title, 80))) AS last_notes_title,
SUBSTRING(last_notes_body, 1, (80 + LOCATE(' ', last_notes_body, 80))) AS last_notes_body
FROM (
SELECT ticket_id, created,
GROUP_CONCAT(title ORDER BY created DESC SEPARATOR '|') AS last_notes_title,
GROUP_CONCAT(body ORDER BY created DESC SEPARATOR '|') AS last_notes_body
FROM ost_ticket_thread WHERE ticket_id IN (1,2,3,4,5) GROUP BY ticket_id
) AS t

Formatowanie daty w mysql

SELECT e.name,
DATE_FORMAT(FROM_UNIXTIME(start_time), '%Y-%d-%m') AS start_date_format,
DATE_FORMAT(FROM_UNIXTIME(start_time), '%H:%i:%S') AS start_time_format,
DATE_FORMAT(FROM_UNIXTIME(end_time), '%H:%i:%S') AS end_time_format,
TIMESTAMPDIFF(MINUTE, FROM_UNIXTIME(start_time),  FROM_UNIXTIME(end_time))
AS diff_time_minutes_format,
e.room_name,
e.start_time,
e.end_time,
WHERE e.room_id = r.id
 AND DATE_FORMAT(FROM_UNIXTIME(e.start_time), '%Y-%m-%d') <= '2016-05-23'
 AND DATE_FORMAT(FROM_UNIXTIME(e.end_time), '%Y-%m-%d') >= '2016-05-19'
 AND e.name IN ('misiek')
ORDER BY
 e.start_time

Jak przyspieszyć WordPress

Poniżej kilka trików które pozwoliły mi przyspieszyć bloga z 32/22 w PageSpeed Insigth do 90/100 – oczywiście to zależy od strony, ilości zdjęć, szybkości serwera itp, wszystko ma znaczenie.
I to jest blog ze zdjęciami (blog.grzegorztomicki.pl), jak dla mnie bardzo zacnie 🙂

1. Będziemy potrzebować dostęp do .htaccess
W którym dodajemy (nie będę opisywał co dokładnie jest w nim robione bo wszystko można znaleźć opisane w internecie)

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
#</IfModule>



<IfModule mod_deflate.c>
  # Compress HTML, CSS, JavaScript, Text, XML and fonts
  AddOutputFilterByType DEFLATE application/javascript
  AddOutputFilterByType DEFLATE application/rss+xml
  AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
  AddOutputFilterByType DEFLATE application/x-font
  AddOutputFilterByType DEFLATE application/x-font-opentype
  AddOutputFilterByType DEFLATE application/x-font-otf
  AddOutputFilterByType DEFLATE application/x-font-truetype
  AddOutputFilterByType DEFLATE application/x-font-ttf
  AddOutputFilterByType DEFLATE application/x-javascript
  AddOutputFilterByType DEFLATE application/xhtml+xml
  AddOutputFilterByType DEFLATE application/xml
  AddOutputFilterByType DEFLATE font/opentype
  AddOutputFilterByType DEFLATE font/otf
  AddOutputFilterByType DEFLATE font/ttf
  AddOutputFilterByType DEFLATE image/svg+xml
  AddOutputFilterByType DEFLATE image/x-icon
  AddOutputFilterByType DEFLATE text/css
  AddOutputFilterByType DEFLATE text/html
  AddOutputFilterByType DEFLATE text/javascript
  AddOutputFilterByType DEFLATE text/plain
  AddOutputFilterByType DEFLATE text/xml

  # Remove browser bugs (only needed for really old browsers)
  BrowserMatch ^Mozilla/4 gzip-only-text/html
  BrowserMatch ^Mozilla/4\.0[678] no-gzip
  BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
  Header append Vary User-Agent
</IfModule>

<IfModule mod_expires.c>
  ExpiresActive on

# Perhaps better to whitelist expires rules? Perhaps.
  ExpiresDefault                          "access plus 1 month"

# cache.appcache needs re-requests in FF 3.6 (thx Remy ~Introducing HTML5)
  ExpiresByType text/cache-manifest       "access plus 0 seconds"



# Your document html
  ExpiresByType text/html                 "access plus 0 seconds"

# Data
  ExpiresByType text/xml                  "access plus 0 seconds"
  ExpiresByType application/xml           "access plus 0 seconds"
  ExpiresByType application/json          "access plus 0 seconds"

# RSS feed
  ExpiresByType application/rss+xml       "access plus 1 hour"

# Favicon (cannot be renamed)
  ExpiresByType image/x-icon              "access plus 1 week"

# Media: images, video, audio
  ExpiresByType image/gif                 "access plus 1 month"
  ExpiresByType image/png                 "access plus 1 month"
  ExpiresByType image/jpg                 "access plus 1 month"
  ExpiresByType image/jpeg                "access plus 1 month"
  ExpiresByType video/ogg                 "access plus 1 month"
  ExpiresByType audio/ogg                 "access plus 1 month"
  ExpiresByType video/mp4                 "access plus 1 month"
  ExpiresByType video/webm                "access plus 1 month"

# HTC files  (css3pie)
  ExpiresByType text/x-component          "access plus 1 month"

# Webfonts
  ExpiresByType font/truetype             "access plus 1 month"
  ExpiresByType font/opentype             "access plus 1 month"
  ExpiresByType application/x-font-woff   "access plus 1 month"
  ExpiresByType image/svg+xml             "access plus 1 month"
  ExpiresByType application/vnd.ms-fontobject "access plus 1 month"

# CSS and JavaScript
  ExpiresByType text/css                  "access plus 1 year"
  ExpiresByType application/javascript    "access plus 1 year"
  ExpiresByType text/javascript           "access plus 1 year"

  <IfModule mod_headers.c>
    Header append Cache-Control "public"
    Header unset ETag
    FileETag None
  </IfModule>

</IfModule>

2. Instalujemy „BJ Lazy Load”
Jest to plugin do „wolnego” ładowania się obrazków więcej na https://wordpress.org/plugins/bj-lazy-load/

3. Instalujemy „WP Super Cache”
Włączamy w nim w zakładce „Easy” na Caching on (Recommended) – więcej na https://wordpress.org/plugins/wp-super-cache/, czy działa można sprawdzić na stronie na samym dole powinien pojawić się mniej więcej taki wpis.

<!--HTML compressed, size saved 9.28%. From 64384 bytes, now 58410 bytes-->
<!-- Dynamic page generated in 0.974 seconds. -->
<!-- Cached page generated by WP-Super-Cache on 2016-01-01 11:42:46 -->


4. Najtrudniejsza część instalujemy „Above The Fold Optimization”

Nie włączamy go.
Klikamy na zakładkę „Extract Full CSS” pojawia się lista wszystkich css które są na stronie oraz textarea z połączonymi wszystkimi plikami css „Full CSS” – textarea.

Kopiuję Ctrl + C do strony https://jonassebastianohlsson.com/criticalpathcssgenerator/

Na tej stronie w pole:
1. URL podaję adres blog lub specyficznej strony na naszym blogu,
2. W „FULL CSS” wklejam cały nasz wygenerowany css.
3. Klikamy „Create Critical Path CSS”, po dłuższej chwili (w zależności od tego jak duży jest css w moim wypadku wszystkie połączone cssy są wielkości ponad 0,5MB) pojawia się okno textarea „CRITICAL PATH CSS” a w nim dostosowane css które kopiuję do pliku header.php od razu pod ,
oczywiście można to zrobić ładniej ale że jestem lazy to mi się nie chciało 🙂

Uwaga z tego skróconego css usuwamy wszystkie @font-face eot,ttf itd czcionki.

5. Wygląd -> Edytor -> otwieramy Funkcje motywu (functions.php)
Na samym końcu dodaję:

<?php
function remove_head_scripts() {
   remove_action('wp_head', 'wp_print_scripts');
   remove_action('wp_head', 'wp_print_head_scripts', 9);
   remove_action('wp_head', 'wp_enqueue_scripts', 1);

   remove_action( 'wp_head', 'rsd_link');
   remove_action( 'wp_head', 'wlwmanifest_link');
   remove_action( 'wp_head', 'wp_generator');
   remove_action( 'wp_head', 'start_post_rel_link');
   remove_action( 'wp_head', 'index_rel_link');
   remove_action( 'wp_head', 'adjacent_posts_rel_link');
   remove_action( 'wp_head', 'wp_shortlink_wp_head');

   add_action('wp_footer', 'wp_print_scripts', 5);
   add_action('wp_footer', 'wp_enqueue_scripts', 5);
   add_action('wp_footer', 'wp_print_head_scripts', 5);
}
add_action( 'wp_enqueue_scripts', 'remove_head_scripts' );

?>

Powyższa funkcja przenosi cały script do sekcji footer

Dodaję również jedną przydatną rzecz czyli kompresję html – usuwanie zbędnych spacji, nowych linii itd, również dodajemy w function.php na samym dole

<?php
class WP_HTML_Compression
{
    // Settings
    protected $compress_css = true;
    protected $compress_js = true;
    protected $info_comment = true;
    protected $remove_comments = true;

    // Variables
    protected $html;
    public function __construct($html)
    {
     if (!empty($html))
     {
         $this->parseHTML($html);
     }
    }
    public function __toString()
    {
     return $this->html;
    }
    protected function bottomComment($raw, $compressed)
    {
     $raw = strlen($raw);
     $compressed = strlen($compressed);
     
     $savings = ($raw-$compressed) / $raw * 100;
     
     $savings = round($savings, 2);
     
     return '<!--HTML compressed, size saved '.$savings.'%. From '.$raw.' bytes, now '.$compressed.' bytes-->';
    }
    protected function minifyHTML($html)
    {
     $pattern = '/<(?<script>script).*?<\/script\s*>|<(?<style>style).*?<\/style\s*>|<!(?<comment>--).*?-->|<(?<tag>[\/\w.:-]*)(?:".*?"|\'.*?\'|[^\'">]+)*>|(?<text>((<[^!\/\w.:-])?[^<]*)+)|/si';
     preg_match_all($pattern, $html, $matches, PREG_SET_ORDER);
     $overriding = false;
     $raw_tag = false;
     // Variable reused for output
     $html = '';
     foreach ($matches as $token)
     {
         $tag = (isset($token['tag'])) ? strtolower($token['tag']) : null;
         
         $content = $token[0];
         
         if (is_null($tag))
         {
             if ( !empty($token['script']) )
             {
                 $strip = $this->compress_js;
             }
             else if ( !empty($token['style']) )
             {
                 $strip = $this->compress_css;
             }
             else if ($content == '<!--wp-html-compression no compression-->')
             {
                 $overriding = !$overriding;
                 
                 // Don't print the comment
                 continue;
             }
             else if ($this->remove_comments)
             {
                 if (!$overriding && $raw_tag != 'textarea')
                 {
                     // Remove any HTML comments, except MSIE conditional comments
                     $content = preg_replace('/<!--(?!\s*(?:\[if [^\]]+]|<!|>))(?:(?!-->).)*-->/s', '', $content);
                 }
             }
         }
         else
         {
             if ($tag == 'pre' || $tag == 'textarea')
             {
                 $raw_tag = $tag;
             }
             else if ($tag == '/pre' || $tag == '/textarea')
             {
                 $raw_tag = false;
             }
             else
             {
                 if ($raw_tag || $overriding)
                 {
                     $strip = false;
                 }
                 else
                 {
                     $strip = true;
                     
                     // Remove any empty attributes, except:
                     // action, alt, content, src
                     $content = preg_replace('/(\s+)(\w++(?<!\baction|\balt|\bcontent|\bsrc)="")/', '$1', $content);
                     
                     // Remove any space before the end of self-closing XHTML tags
                     // JavaScript excluded
                     $content = str_replace(' />', '/>', $content);
                 }
             }
         }
         
         if ($strip)
         {
             $content = $this->removeWhiteSpace($content);
         }
         
         $html .= $content;
     }
     
     return $html;
    }
     
    public function parseHTML($html)
    {
     $this->html = $this->minifyHTML($html);
     
     if ($this->info_comment)
     {
         $this->html .= "\n" . $this->bottomComment($html, $this->html);
     }
    }
   
    protected function removeWhiteSpace($str)
    {
     $str = str_replace("\t", ' ', $str);
     $str = str_replace("\n",  '', $str);
     $str = str_replace("\r",  '', $str);
     
     while (stristr($str, '  '))
     {
         $str = str_replace('  ', ' ', $str);
     }
     
     return $str;
    }
}

function wp_html_compression_finish($html)
{
    return new WP_HTML_Compression($html);
}

function wp_html_compression_start()
{
    ob_start('wp_html_compression_finish');
}
add_action('get_header', 'wp_html_compression_start');
?>

6. Najważniejsza część pracy nad przyspieszeniem strony to oczywiście modyfikacja zdjęć.
Zdjęcia powinny mieć kompresję ustawioną na 70%, usunięcie wszystkich metadanych ze zdjęć, powinny być zapisane jako progressive, wielkość dostosowana do wielkości miejsca w którym się pojawiają zdjęcia.

7. W przyszłości zdjęcie przerobię na nowe rozwiązania dla wersji mobilnych
Więcej na https://scottjehl.github.io/picturefill/examples/demo-01.html oraz https://ericportis.com/posts/2014/srcset-sizes/

Tego rozwiązania używa między innymi theguardian.com a to wygląda mniej więcej tak (to jest jeden plik tylko że w różnych wielkościach):

<picture>
<!--[if IE 9]><video style="display: none;"><![endif]-->
<source media="(min-width: 1300px)" sizes="1300px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=1300&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=10e2200031125f835392892e9f813634 1300w">
<source media="(min-width: 1140px)" sizes="1140px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=1140&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=13d6ffcf420fb95bff93b5ca7a418f8e 1140w">
<source media="(min-width: 980px)" sizes="1125px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=1125&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=63fa2db8b2dccfc0ab408e25551c32e6 1125w">
<source media="(min-width: 740px)" sizes="965px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=965&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=c85b88b5fa49b0854485bd720d689155 965w">
<source media="(min-width: 660px)" sizes="725px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=725&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=048d9b371a22cebba3bb7dcd8e2d619c 725w">
<source media="(min-width: 480px)" sizes="645px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=645&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=97c169eb0122c0552b270a4be96e0410 645w">
<source media="(min-width: 0px)" sizes="465px" srcset="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=465&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=b4476e761590a0a1175cb66c5bfd8a8d 465w">
<!--[if IE 9]></video><![endif]-->
<img class="maxed responsive-img" itemprop="contentUrl" alt="Francesca Chaouqui" src="https://i.guim.co.uk/img/media/2fa72163af4712211cbf9abd929cc377d4af267b/0_0_4928_2957/master/4928.jpg?w=300&amp;q=85&amp;auto=format&amp;sharp=10&amp;s=c69f163a8116e3ac4ad7cc8170a1ad11">
</picture>

A poniżej wynik mojej pracy.

desctop

mobile

W webpagetest też jest bardzo pozytywnie 🙂

webpage

Zapytanie do bazy wordpress


SELECT p2c.category_id AS cat_id, COUNT(p2c.rel_id) AS numposts,
UNIX_TIMESTAMP(max(posts.post_date_gmt)) + '1' AS last_post_date,
UNIX_TIMESTAMP(max(posts.post_date_gmt)) AS last_post_date_gmt
FROM wp_post2cat p2c
INNER JOIN wp_posts posts ON p2c.post_id=posts.id
WHERE 1 = 1
AND posts.post_type = 'post' AND posts.post_status = 'publish'
AND posts.post_date_gmt <= '2014-01-21 14:33:02'
GROUP BY p2c.category_id
ORDER BY numposts DESC

Jedno zapytań do bazy wpordpress.

Dodanie przed nazwą ulicy przedrostka ul.

Dodanie w bazie danych dodać przed nazwą ulicy przedrostek „ul.”

SELECT ulica, concat('ul. ', ulica) FROM miasta_biura WHERE
ulica REGEXP '^ul.' <> 1 AND ulica REGEXP '^Al.' <> 1

UPDATE miasta_biura SET ulica = concat('ul. ', ulica)
WHERE ulica REGEXP '^ul.' <> 1 AND ulica REGEXP '^Al.' <> 1

SELECT ulica FROM miasta_biura WHERE ulica REGEXP '^ul.[^ ]'

SELECT ulica, REPLACE(ulica, 'ul.', 'ul. ') FROM miasta_biura WHERE ulica REGEXP '^ul.[^ ]'

UPDATE miasta_biura SET ulica = REPLACE(ulica, 'ul.', 'ul. ') WHERE ulica REGEXP '^ul.[^ ]'

SELECT ulica, REPLACE(ulica, 'al.', 'Al.') FROM miasta_biura WHERE ulica REGEXP '^Al.'

UPDATE miasta_biura SET ulica = REPLACE(ulica, 'al.', 'Al.') WHERE ulica REGEXP '^Al.'