WOLF
Edytor w stylu arkusza kalkulacyjnego do masowej edycji, filtrowania, eksportu i wycofywania zmian we wpisach, stronach i dowolnym własnym typie wpisu.
- Pierwsze wydanie
- Aktywne instalacje
- 4000+
- Ocena
- Czytaj opinie
O mnie
WOLF (dawniej WPBE) to odpowiednik edytora masowego BEAR dla WooCommerce, ale dla wpisów. Pokazuje wpisy dowolnego zarejestrowanego typu w edytowalnej tabeli, pozwala filtrować je według pól rdzenia, taksonomii i meta, a następnie zmienić, usunąć lub wyeksportować cały przefiltrowany zbiór jedną operacją.
Trudność polega na tym, że nic nie jest stałe. Każdy typ wpisu przynosi własne taksonomie i klucze meta, wartości meta mogą być zserializowanymi tablicami, listami ID obrazów lub flagami z własnymi wartościami, a zbiór docelowy mogą stanowić tysiące wpisów na współdzielonym hostingu z krótkimi limitami czasu PHP.
Wtyczka odpowiada na to rejestrem pól odbudowywanym dla każdego typu wpisu, filtrem zapisywanym na serwerze i kompilowanym do jednego WP_Query, przebiegami masowymi sterowanymi z przeglądarki w partiach o stałym rozmiarze oraz dziennikiem historii, który zapisuje poprzednią wartość każdego pola przed jego zapisem, dzięki czemu przebieg masowy można również cofać partiami.
Wersja premium i wersja darmowa na wordpress.org dzielą jedną bazę kodu; jedna flaga włącza limity wersji darmowej.
WOLF · edytor masowy wpisów
co rozwiązano
Jeden edytor dla dowolnego typu wpisu
shipped · 2019Co to dajePracownicy edytują dowolny rodzaj treści w jednej tabeli, która pozostaje szybka na dużych witrynach, z kolumnami ustawianymi dla osoby i języka.
- Problem
- Wpisy, strony i własne typy wpisów mają różne taksonomie, klucze meta i stosowalne pola rdzenia. Jedna tabela musi pokazywać każdy z nich i pozostać szybka na witrynach z wieloma rekordami.
- Rozwiązanie
Rejestr pól jest odbudowywany dla aktywnego typu wpisu, a tabela działa w trybie serwerowym DataTables na WP_Query, więc każde żądanie czyta jedną stronę wpisów.
- Technologie
Każdy typ wpisu przynosi własny zestaw taksonomiiKolumny taksonomii budowane z get_object_taxonomies() dla aktywnego typu wpisu
WPBE_SETTINGS::get_fields() scala statyczną listę pól z data/fields.php z jedną kolumną na taksonomię zarejestrowaną dla bieżącego typu wpisu, a następnie stosuje zapisaną przez użytkownika kolejność i tytuły kolumn. Nowe pola pojawiające się później (nowa taksonomia, nowy klucz meta) są dopisywane zamiast ginąć.
Definicje pól niosą allow_post_types i prohibit_post_types. wrap_field_val() zwraca komórkę 'restricted', gdy pole nie ma zastosowania, na przykład pola statusu i komentarzy dla załączników, typ mime tylko dla załączników, sticky tylko dla wpisów. Załączniki są odpytywane z dodatkowym statusem 'inherit'.
Duże witryny nie mogą załadować każdego wpisu do przeglądarkiDataTables po stronie serwera: każda strona, sortowanie i wyszukiwanie to jedno stronicowane WP_Query
Tabela jest skonfigurowana z serverSide: true i wywołuje wpbe_get_posts z parametrami stronicowania DataTables. WPBE_POSTS::gets() zamienia start/length na paged/posts_per_page i przełącza się na sortowanie meta_value_num, gdy kolumną sortowania jest liczbowe lub datowe pole meta.
Wiersze na stronę są ograniczone do 100 z komentarzem w kodzie 'to avoid 500 error on weak servers'. Wersja darmowa ogranicza je do 10.
Kod edytora nie może spowalniać reszty wp-adminRozszerzenia i modele ładują się tylko na stronie edytora i przy akcjach AJAX wpbe_*
init() najpierw wykonuje sprawdzenie roli, a potem is_should_init(), które zwraca true tylko dla page=wpbe lub akcji AJAX, której nazwa zawiera 'wpbe_'. Na innych ekranach administracyjnych rejestrowane są tylko pozycja menu, przycisk na pasku administracyjnym i powiadomienia.
Rozszerzenia podążają za konwencją nazewniczą (ext/<slug>/<slug>.php, klasa WPBE_<SLUG>) i dostają magazyn, ustawienia oraz model wpisów przez init_vars(). Ten sam loader podejmuje też rozszerzenia zewnętrzne z wp-content/wpbe_ext.
Każdy użytkownik potrzebuje własnych kolumn, w razie potrzeby osobno dla typu wpisuOpcje zapisane według ID użytkownika, opcjonalnie rozdzielone według typu wpisu (ustawienie premium)
Ustawienia żyją w opcji wpbe_options_<user_id>. Gdy przełącznik 'separate settings' jest włączony, do klucza dopisywany jest typ wpisu, a lista zarejestrowanych pól meta jest dzielona według typu wpisu w ten sam sposób. Aktywny typ wpisu jest zapamiętywany dla użytkownika w magazynie wtyczki.
Przełącznik jest wyłączony w wersji darmowej.
Na witrynach WPML edytor musi działać w bieżącym języku panelu administracyjnegoBieżący język WPML przekazywany do każdego zapytania; lista przypiętych (sticky) prowadzona dla każdego języka
Gdy obecny jest SitePress, filtr wpbe_current_language zwraca ICL_LANGUAGE_CODE, gets() wywołuje $sitepress->switch_lang() przed zbudowaniem WP_Query, a opcja przypiętych wpisów jest czytana i zapisywana pod sticky_posts_<lang>.
Masowa edycja, usuwanie i eksport partiami
shipped · 2019Co to dajeZmiany w tysiącach wpisów kończą się niezawodnie na zwykłym hostingu, z widocznym postępem i możliwością zatrzymania w połowie.
- Problem
- Edycja tysięcy wpisów w jednym żądaniu PHP trafia na limity czasu i pamięci, a użytkownik potrzebuje postępu i możliwości zatrzymania przebiegu.
- Rozwiązanie
Przeglądarka steruje przebiegiem w partiach ID wpisów o stałym rozmiarze; sama operacja jest zapisywana raz na serwerze pod losowym kluczem masowym.
- Technologie
Jedno żądanie PHP nie zedytuje tysięcy wpisówID ustalane raz, potem wysyłane partiami po 50 do bezstanowego punktu końcowego roboczego
wpbe_bulk_posts_count zapisuje formularz masowy pod 'wpbe_bulk_<key>' (16 losowych znaków generowanych w przeglądarce) i zwraca ID pasujące do bieżącego filtra, używając fields=ids i no_found_rows. Przeglądarka tnie tę listę na partie po 50 i wysyła je jedna po drugiej do wpbe_bulk_posts, aktualizując pasek postępu i wskaźnik na pasku administracyjnym. Anulowanie przerywa oczekujące XHR; wpbe_bulk_finish zwraca policzoną sumę.
Masowe usuwanie używa tego samego wzorca z partiami po 10 i wp_trash_post(), więc usunięte wpisy pozostają do odzyskania z kosza.
Po przebiegu przeglądarka przerysowuje tylko dotknięte wiersze, gdy zmieniono cztery wpisy lub mniej, a w przeciwnym razie przerysowuje bieżącą stronę.
Pola tekstowe potrzebują edycji względem bieżącej wartościDopisz na końcu, na początku, zamień (z rozróżnianiem wielkości liter lub bez), ustaw nową; makra dla każdego wpisu
__process_text_data() czyta bieżącą wartość każdego wpisu i stosuje wybrane zachowanie. Wartości mogą zawierać {TITLE}, {ID}, {MENU_ORDER} i {meta:key}, rozwiązywane dla wpisu, oraz makra wielkości liter, takie jak {DO_STRING_UP} lub {DO_STRING_TITLE}, które używają funkcji mb_*, więc tytuły nielacińskie są obsługiwane.
Liczbowe meta potrzebuje zmian względnych i zaokrąglania jak dla cenO wartość lub procent, formuła z innym kluczem meta, losowy zakres, zaokrąglanie do .05, .10 lub .x9
__process_number_data() obsługuje zwiększenie/zmniejszenie o wartość lub procent, a następnie opcjonalnie łączy wynik z innym polem meta tego samego wpisu (+, -, *, /, z dzieleniem przez zero zastąpionym przez 1) oraz z liczbą losową z zakresu skalowaną współczynnikiem dziesiętnym.
Zaokrąglanie ułamka do kroków .05 lub .10, albo do końcówki takiej jak .99, odbywa się w update_page_field() w skonfigurowanej liczbie miejsc dziesiętnych; grosze są przeliczane przez całkowity krok, ponieważ modulo na liczbach zmiennoprzecinkowych daje błędne wyniki.
Taksonomie hierarchiczne i płaskie potrzebują różnego wprowadzania terminówID terminów dla taksonomii hierarchicznych, slugi terminów jeden po drugim dla płaskich
WordPress oczekuje ID dla taksonomii hierarchicznych i nazw lub slugów dla typu tagów. Worker masowy sprawdza is_taxonomy_hierarchical(); dla taksonomii płaskich zamienia każde ID na slug, a dla 'replace' zapisuje pierwszy termin z append=false i resztę z append=true. 'Remove' używa wp_remove_object_terms().
Hook historii jest wywoływany ręcznie przed zmianami terminów, ponieważ ta ścieżka omija update_page_field().
Eksporty dużych zaznaczeń trafiają na te same limityCSV dopisywane partiami po 10 przez fputcsv; XML zapisywany w formacie WXR WordPressa
Krok liczenia zapisuje wiersz nagłówka; każda partia dopisuje wiersze przez fputcsv() do pliku otwartego w trybie a+. Kod zaznacza, że WP_Filesystem nie ma dopisywania linia po linii, dlatego tu używane są surowe funkcje plikowe.
Eksport XML buduje dokument RSS z przestrzeniami nazw WXR 1.2 (wp:, content:, excerpt:), terminy jako <category domain nicename> i meta jako wp:postmeta z CDATA, więc plik podąża za formatem eksportera WordPressa. Nazwy plików niosą datę i minutę eksportu, a pliki starsze niż dwa dni są usuwane przez sprawdzenie ograniczone transientem.
Filtrowanie według pól, terminów i meta
shipped · 2019Co to dajePracownicy wybierają dokładnie te wpisy, które chcą, według pól, kategorii, własnych wartości lub wklejonych list ID, a edycje i eksporty dotyczą tego samego zaznaczenia.
- Problem
- Edycja masowa i eksport dotyczą wszystkiego, co pasuje do filtra, więc filtr musi zamienić wiele warunków pól w jedno poprawne WP_Query i pozostać identyczny w osobnych żądaniach AJAX i kartach przeglądarki.
- Rozwiązanie
Stan filtra jest zapisywany na serwerze pod losowym kluczem karty i kompilowany do klauzul tax_query, meta_query i posts_where w jednym hooku używanym przez każde zapytanie.
- Technologie
Żądania tabeli, edycji masowej i eksportu muszą widzieć ten sam filtrFiltr zapisany po stronie serwera pod 16-znakowym kluczem generowanym dla każdej karty przeglądarki
Przeglądarka generuje wpbe_filter_current_key przy zastosowaniu filtra i wysyła go z każdym żądaniem; komentarz w źródle mówi, że pozwala to na edycję masową według filtra w różnych kartach. wpbe_apply_query_filter_data() czyta zapisany filtr i jest stosowana wewnątrz WPBE_POSTS::gets(), więc lista ID do edycji masowej jest budowana tym samym kodem co widoczna tabela.
Dopasowanie wszystkich z kilku terminów wymaga jednej klauzuli tax_query na terminAND rozbity na jedną klauzulę tax_query na termin; dodane EXISTS i NOT EXISTS
Dla operatora AND każdy wybrany termin staje się własną klauzulą łączoną zewnętrzną relacją AND, co jest obejściem, do którego odwołuje się komentarz w kodzie. Inne operatory (IN, NOT IN) przekazują listę terminów bez zmian. include_children jest sterowane filtrem zamiast domyślnym zachowaniem WordPressa.
Zakresy liczbowego meta potrzebują rzutowania na DECIMAL z jawną skaląBETWEEN z rzutowaniem DECIMAL(30,20); obsłużone granice jednostronne i równe
Komentarz w kodzie podaje przypadek: przy DECIMAL bez precyzji wyszukiwanie od 100 do 150 zwracało też 99.xx. Filtr używa DECIMAL(30,20), przełącza się na >=, gdy ustawione jest tylko 'od', i na =, gdy obie granice są równe. Meta z datą przechodzi najpierw przez normalize_calendar_date().
Puste meta może być '' albo brakującym wierszem'Empty' kompiluje się do value = '' OR NOT EXISTS; 'not empty' do value != ''
Wpisy utworzone przed wprowadzeniem klucza meta nie mają żadnego wiersza, więc samo sprawdzenie równości je pomija. Zachowanie 'empty' emituje zagnieżdżone meta_query z relacją OR.
Wyszukiwanie tekstu potrzebuje OR, AND i NOT bez surowego SQLMała składnia (^ dla OR, ~ dla AND, ! dla NOT) kompilowana do przygotowanego REGEXP
posts_txt_where() zamienia dane wejściowe na małe litery, dzieli po ^ i buduje warunki LOWER(column) REGEXP / NOT REGEXP z trybami begin, end, exact i not. Każda wartość jest wiązana przez $wpdb->prepare(), a kolumna musi być na liście dozwolonych pięciu kolumn wp_posts.
Użytkownicy wklejają listy ID i zakresyWejście takie jak '12,40-55' rozwijane do post__in
Elementy rozdzielone przecinkami są traktowane jako ID; element z myślnikiem jest rozwijany do pełnego zakresu niezależnie od kolejności. Zakresy dat i liczb na kolumnach wp_posts (menu_order, post_parent, daty wpisu) przechodzą przez osobny filtr posts_where z listą dozwolonych kolumn.
Historia i cofanie zmian
shipped · 2019Co to dajeBłędną edycję masową można cofnąć jednym krokiem zamiast poprawiać tysiące wpisów ręcznie.
- Problem
- Błędną masową edycję tysięcy wpisów trudno cofnąć: rewizje WordPressa nie obejmują meta, terminów ani większości pól rdzenia, a samo cofnięcie może być tak duże jak edycja.
- Rozwiązanie
Każdy zapis pola jest poprzedzony wierszem dziennika z poprzednią wartością; wiersze jednego przebiegu masowego dzielą jego klucz i są cofane partiami.
- Technologie
Zapisać starą wartość dowolnego typu pola, zanim się zmieniHook przed każdym zapisem zapisuje poprzednią wartość w wp_wpbe_history
write() jest podpięte do wpbe_before_update_page_field, które update_page_field() wywołuje przed każdym zapisem; masowa ścieżka taksonomii, która zapisuje terminy bezpośrednio, wywołuje je ręcznie. Wartości taksonomii są zapisywane jako lista JSON ID terminów, meta tablicowe jako JSON (z JSON_HEX_QUOT i JSON_HEX_TAG dla edytora zserializowanego meta), skalary bez zmian. Każdy wiersz ma user_id i post_type, oraz klucz masowy, gdy należy do przebiegu.
Tysiące zmian muszą tworzyć jedną operację do cofnięciaTabela nagłówkowa wpbe_history_bulk na przebieg: klucze, liczba wpisów i stan
start_bulk() wstawia wiersz nagłówka z listą edytowanych pól jako JSON; każda partia dodaje swój rozmiar przez UPDATE posts_count = posts_count + %d; finish_bulk() oznacza przebieg jako completed. Przebieg anulowany przez użytkownika zachowuje domyślny stan 'terminated', więc lista pokazuje go jako częściowy. Obie tabele są tworzone przez dbDelta z zestawem znaków witryny, indeksowane po bulk_key i user_id oraz usuwane przy odinstalowaniu.
Cofanie dużego przebiegu trafia na te same limity czasuCofanie porcjami po 10 wierszy na żądanie; wiersz nagłówka usuwany na końcu
Przeglądarka pyta o liczbę wierszy dla klucza masowego, a następnie wielokrotnie wywołuje wpbe_history_revert_bulk_portion z limitem 10 i paskiem postępu. Każdy cofnięty wiersz jest usuwany, więc następna porcja podejmuje pozostałe; wiersz nagłówka jest usuwany, gdy licznik osiągnie sumę.
Cofnięcie nie może samo rejestrować się jako nowa zmianaremove_all_actions() na hooku logowania przed zapisem starych wartości
revert() odpina zapisującego historię, dekoduje zapisaną wartość według typu pola (listy ID terminów, tablice meta JSON) i zapisuje ją z powrotem tym samym update_page_field() używanym przy edycjach, więc save_post i edit_post odpalają przy cofnięciu tak jak przy edycji.
Pojedyncze edycje i przebiegi masowe muszą pokazywać się na jednej osi czasuPojedyncze wiersze i nagłówki masowe scalane i sortowane według czasu, dla użytkownika i typu wpisu
get_history() czyta pojedyncze wiersze i nagłówki masowe bieżącego użytkownika i typu wpisu, scala je i sortuje według czasu, od najnowszych, więc jedna lista pokazuje oba rodzaje zmian.
Wersja darmowa pokazuje dwa najnowsze wpisy i przycina starsze pojedyncze wiersze oraz nagłówki masowe po otwarciu listy.
Pola meta i treść strukturalna
shipped · 2019Co to dajeWłasne dane, takie jak listy, flagi i galerie obrazów, można edytować bezpiecznie, bez ich psucia, nawet gdy pracownicy nie wiedzą, jak są przechowywane.
- Problem
- Własne typy wpisów trzymają większość danych w postmeta, często jako zserializowane tablice, listy ID obrazów lub flagi z wartościami specyficznymi dla witryny, które zwykła komórka tekstowa by uszkodziła.
- Rozwiązanie
Klucze meta są rejestrowane z typem i widokiem edycji, a każdy widok ma własny czytnik, zapisujący, filtr i handler masowy.
- Technologie
Administratorzy nie wiedzą, jakich kluczy meta używa typ wpisuKlucze czytane z przykładowego wpisu; zarejestrowane klucze stają się kolumnami, filtrami, polami edycji masowej
wpbe_meta_get_keys zwraca klucze meta wpisu o ID podanym przez administratora. Zarejestrowany klucz jest wstrzykiwany przez wpbe_extend_fields oraz hooki wpbe_filter_* i wpbe_bulk_*, więc pojawia się w tabeli, panelu filtrów i panelu edycji masowej naraz. Klucze są przycinane, ale nie zamieniane na małe litery, ponieważ klucze takie jak _woocs_sale_price_USD rozróżniają wielkość liter.
Wersja darmowa używa tylko dwóch pierwszych zarejestrowanych pól meta.
Zserializowane tablice trzeba edytować bez psucia strukturyKreator w oknie wysyła tablice klucz/wartość; serwer odbudowuje zagnieżdżone tablice
Okno wysyła keys[]/values[] dla tablic płaskich i zagnieżdżonych oraz keys2[][]/values2[][] dla list obiektów. __process_jsoned_meta_data() odbudowuje tablicę PHP, dekoduje wartości będące poprawnym JSON i dla zwykłych ciągów wraca do surowej wartości. update_post_meta() zapisuje ją potem zserializowaną, jak oczekuje WordPress. Ta sama ścieżka jest używana przez worker masowy.
Flagi są przechowywane jako 'yes'/'no' lub inne pary specyficzne dla witrynyReguły mapowania 'key:on^off' tłumaczą między przełącznikiem 1/0 a zapisanymi wartościami
over_switcher_swicher_to_val() i over_switcher_val_to_swicher() czytają ciąg ustawień taki jak 'dist:yes,stest:true' i konwertują w obu kierunkach, więc interfejs przełącznika działa z wartościami zapisanymi przez inne wtyczki.
Galerie obrazów trzymane w meta jako listy ID załącznikówTyp meta galerii zapisywany jako lista ID załączników rozdzielona przecinkami
Okno galerii wysyła formularz zakodowany w URL; handler wykrywa kodowanie, parsuje go przez parse_str(), sanityzuje i zapisuje ID jako ciąg rozdzielony przecinkami. Worker masowy akceptuje ten sam format.
Treści edytora blokowego nie da się edytować w zwykłym textareaWpis otwierany w edytorze blokowym w iframe; treść czytana przez wp.data
Opcja 'Use gutenberg'. Dla typów wpisów używających edytora blokowego okno ładuje post.php z wpbe_popup_editor=1; wp_editor_compatibility() ukrywa w tej ramce kontrolki publikacji i panelu bocznego oraz udostępnia funkcję zwracającą wp.data.select('core/editor').getEditedPostContent(). Strona nadrzędna ją czyta i zapisuje zwykłą aktualizacją pola.
Kontrola dostępu i utwardzanie żądań
shipped · 2023Co to dajeZ edytora mogą korzystać tylko zaufane role, autorzy pracują wyłącznie z własnymi wpisami, a pracownicy widzą tylko pola, które wolno im zmieniać.
- Problem
- Narzędzie, które może masowo przepisać dowolne pole dowolnego wpisu, to cenny cel: każdy punkt końcowy AJAX musi sprawdzać wywołującego, a dane filtra trafiają blisko SQL.
- Rozwiązanie
Jedna brama ról przed zarejestrowaniem jakiegokolwiek punktu końcowego, nonce przy akcjach pól, masowych, narzędzi i ustawień, prawa na poziomie pól dla ról innych niż administrator oraz przygotowany SQL z listami dozwolonych kolumn.
- Technologie
Tylko zaufane role mogą dotrzeć do jakiegokolwiek punktu końcowego edytoraSprawdzenie roli w init() działa przed dodaniem jakiegokolwiek handlera wp_ajax_wpbe_*
can_manage_data() dopuszcza administratorów, redaktorów i role dodane przez filtr wpbe_author_area_roles. Jeśli sprawdzenie się nie powiedzie, init() wraca przed załadowaniem rozszerzeń, więc akcje AJAX nie istnieją dla tego użytkownika. Aktualizacje pól, edycja i usuwanie masowe, panel narzędzi (tworzenie, duplikowanie, usuwanie, terminy), ustawienia meta, formularz opcji, lista historii i pojedyncze cofnięcia również sprawdzają nonce.
Redaktorzy nie powinni widzieć ani zmieniać każdego pola i typu wpisuAdministrator ustawia widoczność pól i dozwolone typy wpisów dla ról innych niż administrator (premium)
Dla ról innych niż administrator get_fields() odrzuca ukryte kolumny, więc nie są oferowane w tabeli ani panelu edycji masowej; update_page_field() sprawdza is_current_user_can_edit_field() i zwraca 'forbidden' dla ukrytego pola; filter_post_types() zawęża selektor typu wpisu, a konstruktor ustawień przenosi użytkownika na dozwolony typ wpisu. W wersji darmowej checkboxy widoczności są renderowane jako wyłączone.
Autorzy powinni pracować tylko z własnymi wpisamiRole obszaru autora dostają author=self w zapytaniach edytora i nie mogą zmieniać post_author
Dla ról dodanych przez filtr wpbe_author_area_roles WPBE_AUTHOR_AREA dodaje 'author' => ID bieżącego użytkownika przez filtr zapytania używany przez tabelę, listę ID do edycji masowej i eksport, więc te listy pokazują tylko własne wpisy użytkownika. Jej callback wpbe_user_can_edit blokuje post_author dla tych ról.
Dane filtra i skopiowane dane wpisu trafiają do SQLNazwy kolumn z list dozwolonych, każda wartość związana przez $wpdb->prepare()
Filtry tekstu i zakresów akceptują tylko wymienione kolumny wp_posts i wiążą wartości przez prepare(). Zapytania historii wiążą użytkownika, typ wpisu i klucz masowy oraz budują listy NOT IN z symboli zastępczych array_fill().
Duplikowanie kopiuje terminy według sluga i wszystkie postmeta poza _wp_old_slug jedną instrukcją INSERT ... SELECT %d, %s, %s UNION ALL, z każdym wierszem zbudowanym przez prepare(), a następnie ustawia kopię jako wersję roboczą.
Pola treści muszą zachować osadzenia, ale odrzucić skryptywp_kses z listą dozwolonych dla wpisu plus wąska lista atrybutów iframe
wpbe_update_page_field() sanityzuje tablice rekurencyjnie przez wp_kses i listę dozwolonych dla wpisu. Dla treści, zajawki i pól tekstu sformatowanego dodaje iframe ze stałym zestawem atrybutów, aby osadzenia wideo przetrwały. Filtr może przełączyć na sanitize_text_field() dla bardziej rygorystycznych witryn.
Liczby
wordpress.org- Aktywne instalacje 4000+ licznik wordpress.org.
- Opinie 27 Średnia ocena: 4.7 na 5.
- Lat wsparcia 7 Od 3 września 2019, ostatnia aktualizacja 12 października 2026.