From e5a8505c41ee49cf4da2031a8e460dff7b004aa4 Mon Sep 17 00:00:00 2001
From: dyszczo <krzysztof.dyszczyk@gmail.com>
Date: Fri, 29 Nov 2019 18:48:34 +0100
Subject: [PATCH] first version

---
 .gitattributes                                |  12 +
 .gitignore                                    |   3 +-
 CHANGELOG.md                                  |   3 +
 LICENSE.md                                    |  21 +
 README.md                                     |   9 +
 apigen.neon                                   |  27 +
 composer.json                                 |  47 ++
 lang/wpdesk-rating-petition-pl_PL.po          | 683 ++++++++++++++++++
 phpunit-integration.xml                       |  32 +
 phpunit-unit.xml                              |  21 +
 src/RatingPetitionNotice.php                  | 205 ++++++
 src/TimeWatcher.php                           |  22 +
 .../ShippingMethodGlobalSettingsWatcher.php   |  88 +++
 .../ShippingMethodInstanceWatcher.php         | 174 +++++
 tests/integration/bootstrap.php               |  26 +
 tests/unit/bootstrap.php                      |   6 +
 16 files changed, 1378 insertions(+), 1 deletion(-)
 create mode 100644 .gitattributes
 create mode 100644 CHANGELOG.md
 create mode 100644 LICENSE.md
 create mode 100644 README.md
 create mode 100644 apigen.neon
 create mode 100644 composer.json
 create mode 100644 lang/wpdesk-rating-petition-pl_PL.po
 create mode 100644 phpunit-integration.xml
 create mode 100644 phpunit-unit.xml
 create mode 100644 src/RatingPetitionNotice.php
 create mode 100644 src/TimeWatcher.php
 create mode 100644 src/TimeWatcher/ShippingMethodGlobalSettingsWatcher.php
 create mode 100644 src/TimeWatcher/ShippingMethodInstanceWatcher.php
 create mode 100644 tests/integration/bootstrap.php
 create mode 100644 tests/unit/bootstrap.php

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..6b71fc7
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,12 @@
+.git/ export-ignore
+tests/ export-ignore
+vendor/ export-ignore
+.editorconfig export-ignore
+.gitattributes export-ignore
+.gitignore export-ignore
+.gitlab-ci.yml export-ignore
+.idea export-ignore
+apigen.neon export-ignore
+phpcs.xml.dist export-ignore
+phpunit-integration.xml export-ignore
+phpunit-unit.xml export-ignore
diff --git a/.gitignore b/.gitignore
index 5e97e71..b4d29f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@
 .idea
 composer.lock
 build-coverage
-docker-compose.yml
\ No newline at end of file
+docker-compose.yml
+*.mo
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..b4d942c
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+## [1.0.0] - 2019-11-29
+### Added
+- First version
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..11a689d
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 wpdesk
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..982ba2a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,9 @@
+[![pipeline status](https://gitlab.com/wpdesk/wp-wpdesk-rating-petition/badges/master/pipeline.svg)](https://gitlab.com/wpdesk/wp-wpdesk-rating-petition/pipelines) 
+[![coverage report](https://gitlab.com/wpdesk/wp-wpdesk-rating-petition/badges/master/coverage.svg)](https://gitlab.com/wpdesk/wp-wpdesk-rating-petition/commits/master) 
+[![Latest Stable Version](https://poser.pugx.org/wpdesk/wp-wpdesk-rating-petition/v/stable)](https://packagist.org/packages/wpdesk/wp-wpdesk-rating-petition) 
+[![Total Downloads](https://poser.pugx.org/wpdesk/wp-wpdesk-rating-petition/downloads)](https://packagist.org/packages/wpdesk/wp-wpdesk-rating-petition) 
+[![Latest Unstable Version](https://poser.pugx.org/wpdesk/wp-wpdesk-rating-petition/v/unstable)](https://packagist.org/packages/wpdesk/wp-wpdesk-rating-petition) 
+[![License](https://poser.pugx.org/wpdesk/wp-wpdesk-rating-petition/license)](https://packagist.org/packages/wpdesk/wp-wpdesk-rating-petition)
+
+WordPress Library to ask for good rate in repository.
+===================================================
diff --git a/apigen.neon b/apigen.neon
new file mode 100644
index 0000000..867217f
--- /dev/null
+++ b/apigen.neon
@@ -0,0 +1,27 @@
+destination: docs
+templateConfig: /app/theme-woocommerce/config.neon
+extensions: [php]
+source:
+    - src
+exclude:
+    - vendor
+    - tests
+    - lang
+
+charset: [UTF-8]
+main: Wordpress plugin
+title: Plugin template more info
+baseUrl: "/"
+
+templateTheme: default
+php: false
+sourceCode: false
+tree: true
+deprecated: false
+todo: false
+download: false
+
+accessLevels:       
+    - public
+    - private
+    - protected
\ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..12d2005
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,47 @@
+{
+    "name": "wpdesk/wp-wpdesk-rating-petition",
+    "authors": [
+        {
+            "name": "Krzysiek",
+            "email": "krzysiek@wpdesk.pl"
+        }
+    ],
+    "require": {
+        "php": ">=5.6",
+        "wpdesk/wp-notice": "^3.1"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "<7",
+        "wp-coding-standards/wpcs": "^0.14.1",
+        "squizlabs/php_codesniffer": "^3.0.2",
+        "mockery/mockery": "*",
+        "10up/wp_mock": "*",
+        "wimg/php-compatibility": "^8"
+    },
+
+    "autoload": {
+        "psr-4": {
+            "WPDesk\\RepositoryRating\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "classmap": ["tests/"]
+    },
+    "extra": {
+        "text-domain": "wpdesk-rating-petition",
+        "translations-folder": "lang",
+        "po-files": {
+            "pl_PL": "wpdesk-rating-petition-pl_PL.po"
+        }
+    },
+    "scripts": {
+        "test": "echo composer is alive",
+        "phpcs": "phpcs",
+        "phpunit-unit": "phpunit --configuration phpunit-unit.xml --coverage-text --colors=never",
+        "phpunit-unit-fast": "phpunit --configuration phpunit-unit.xml --no-coverage",
+        "phpunit-integration": "phpunit --configuration phpunit-integration.xml --coverage-text --colors=never",
+        "phpunit-integration-fast": "phpunit --configuration phpunit-integration.xml --no-coverage",
+
+	    "docs": "apigen generate"
+    }
+}
diff --git a/lang/wpdesk-rating-petition-pl_PL.po b/lang/wpdesk-rating-petition-pl_PL.po
new file mode 100644
index 0000000..a9617c4
--- /dev/null
+++ b/lang/wpdesk-rating-petition-pl_PL.po
@@ -0,0 +1,683 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: WP Desk Helper\n"
+"POT-Creation-Date: 2019-11-29 18:46+0100\n"
+"PO-Revision-Date: 2019-11-29 18:47+0100\n"
+"Last-Translator: Krzysztof Dyszczyk <krzysztof.dyszczyk@gmail.com>\n"
+"Language-Team: Polish\n"
+"Language: pl_PL\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2.4\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2);\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+"X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;"
+"esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c;_n_noop:1,2;"
+"_nx_noop:3c,1,2;__ngettext_noop:1,2\n"
+"X-Poedit-Basepath: ../src\n"
+"X-Poedit-SearchPath-0: .\n"
+"X-Poedit-SearchPathExcluded-0: *.js\n"
+
+#: RatingPetitionNotice.php:160
+#, php-format
+msgid ""
+"Awesome, you've been using %s for more than 2 weeks. Could you please do me "
+"a BIG favor and give it a 5-star rating on WordPress? ~ Peter"
+msgstr ""
+"Fantastycznie, korzystasz z %s już ponad 2 tygodnie. Czy możesz wyświadczyć "
+"mi wielką przysługę i wystawić 5-gwiazdkową ocenę? ~Piotr"
+
+#: RatingPetitionNotice.php:175
+#, php-format
+msgid "%1$sOk, you deserved it%2$s"
+msgstr "%1$sTak, zasługujecie na nią%2$s"
+
+#: RatingPetitionNotice.php:181
+#, php-format
+msgid "%1$sNope, maybe later%2$s"
+msgstr "%1$sNie, może później%2$s"
+
+#: RatingPetitionNotice.php:187
+#, php-format
+msgid "%1$sI already did%2$s"
+msgstr "%1$sJuż to zrobiłem/zrobiłam%2$s"
+
+#, php-format
+#~ msgid ""
+#~ "We recommend to <a href=\"%s\">deactivate and remove</a> the \"WP Desk "
+#~ "Helper\" plugin as it is no longer required by WP Desk plugins"
+#~ msgstr ""
+#~ "Zalecamy <a href=\"%s\">dezaktywację i usunięcie wtyczki</a> \"WP Desk "
+#~ "Helper\", ponieważ nie jest już wymagana przez wtyczki WP Desk"
+
+#, php-format
+#~ msgid ""
+#~ "We recommend to <a href=\"%s\">remove</a> the \"WP Desk Helper\" plugin "
+#~ "as it is no longer required by WP Desk plugins"
+#~ msgstr ""
+#~ "Zalecamy <a href=\"%s\">usunięcie wtyczki</a> \"WP Desk Helper\", "
+#~ "ponieważ nie jest już wymagana przez wtyczki WP Desk"
+
+#~ msgid "Debug"
+#~ msgstr "Debug"
+
+#~ msgid "WP Desk Debug Log"
+#~ msgstr "WP Desk Debug Log"
+
+#~ msgid "Enable"
+#~ msgstr "Włącz"
+
+#, php-format
+#~ msgid "Writes error log to %s."
+#~ msgstr "Zapisuje log błędów do %s."
+
+#, php-format
+#~ msgid ""
+#~ "WP Desk Debug Log is enabled. %1$sPlease disable it after testing%2$s."
+#~ msgstr "WP Desk Debug Log jest włączony. %sWyłącz go po testach%s."
+
+#~ msgid "Plugin usage tracking"
+#~ msgstr "Dane użytkowania wtyczek"
+
+#~ msgid "Allow WP Desk to track plugin usage"
+#~ msgstr "Pozwól WP Desk na zbieranie danych użytkowania wtyczek"
+
+#, php-format
+#~ msgid "No sensitive data is tracked, %sread more%s."
+#~ msgstr "Żadne wrażliwe dane nie będą zbierane, %sprzeczytaj więcej%s."
+
+#~ msgid "Library report"
+#~ msgstr "Raport bibliotek"
+
+#~ msgid "Settings"
+#~ msgstr "Ustawienia"
+
+#~ msgid "WP Desk Helper Settings"
+#~ msgstr "Ustawienia WP Desk Helper"
+
+#, php-format
+#~ msgid ""
+#~ "Zalecamy <a href=\"%s\">deaktywację i usunięcie wtyczki</a> \"WP Desk "
+#~ "Helper\", ponieważ nie jest już wymagana przez wtyczki WP Desk"
+#~ msgstr ""
+#~ "We recommend to <a href=\"%s\">deactivate and remove</a> the \"WP Desk "
+#~ "Helper\" plugin as it is no longer required by WP Desk plugins"
+
+#, php-format
+#~ msgid ""
+#~ "Zalecamy <a href=\"%s\">usunięcie wtyczki</a> \"WP Desk Helper\", "
+#~ "ponieważ nie jest już wymagana przez wtyczki WP Desk"
+#~ msgstr ""
+#~ "We recommend to <a href=\"%s\">remove</a> the \"WP Desk Helper\" plugin "
+#~ "as it is no longer required by WP Desk plugins"
+
+#, php-format
+#~ msgid ""
+#~ "The &#8220;%s&#8221; plugin cannot run on PHP versions older than %s. "
+#~ "Please contact your host and ask them to upgrade."
+#~ msgstr ""
+#~ "Wtyczka &#8220;%s&#8221; nie może działać w wersjach PHP starszych niż "
+#~ "%s. Skontaktuj się z administratorem hostingu i poproś o aktualizację."
+
+#, php-format
+#~ msgid ""
+#~ "The &#8220;%s&#8221; plugin cannot run on WordPress versions older than "
+#~ "%s. Please update WordPress."
+#~ msgstr ""
+#~ "Wtyczka &#8220;%s&#8221; nie może działać w wersjach WordPress starszych "
+#~ "niż %s. Prosimy o aktualizację WordPress."
+
+#, php-format
+#~ msgid ""
+#~ "The &#8220;%s&#8221; plugin cannot run on WooCommerce versions older than "
+#~ "%s. Please update WooCommerce."
+#~ msgstr ""
+#~ "Wtyczka &#8220;%s&#8221; nie może działać w wersjach WooCommerce "
+#~ "starszych niż %s. Prosimy o aktualizację WooCommerce."
+
+#, php-format
+#~ msgid ""
+#~ "The &#8220;%s&#8221; plugin cannot run without OpenSSL module version at "
+#~ "least %s. Please update OpenSSL module."
+#~ msgstr ""
+#~ "Wtyczka &#8220;%s&#8221; nie może działać bez aktywacji OpenSSL co "
+#~ "najmniej w wersji %s. Zaktualizuj wersję OpenSSL."
+
+#, php-format
+#~ msgid ""
+#~ "The &#8220;%s&#8221; plugin cannot run without %s active. Please install "
+#~ "and activate %s plugin."
+#~ msgstr ""
+#~ "Wtyczka &#8220;%s&#8221; nie może działać bez aktywacji %s. Zainstaluj i "
+#~ "aktywuj wtyczkę %s."
+
+#, fuzzy, php-format
+#~| msgid ""
+#~| "The &#8220;%s&#8221; plugin cannot run without %s active. Please install "
+#~| "and activate %s plugin."
+#~ msgid ""
+#~ "The &#8220;%s&#8221; plugin requires activating %s plugin. <a href=\"%s"
+#~ "\">Activate %s →</a>"
+#~ msgstr ""
+#~ "Wtyczka &#8220;%s&#8221; nie może działać bez aktywacji %s. Zainstaluj i "
+#~ "aktywuj wtyczkę %s."
+
+#, php-format
+#~ msgid ""
+#~ "The &#8220;%s&#8221; plugin cannot run without %s php module installed. "
+#~ "Please contact your host and ask them to install %s."
+#~ msgstr ""
+#~ "Wtyczka &#8220;%s&#8221; nie może działać bez modułu php: %s. Skontaktuj "
+#~ "się z administratorem hostingu i poproś o instalację %s."
+
+#, php-format
+#~ msgid ""
+#~ "The &#8220;%s&#8221; plugin cannot run without %s php setting set to %s. "
+#~ "Please contact your host and ask them to set %s."
+#~ msgstr ""
+#~ "Wtyczka &#8220;%s&#8221; nie może działać bez ustawienia php %s "
+#~ "ustawionego na %s. Skontaktuj się z administratorem hostingu i poproś o "
+#~ "ustawienie %s."
+
+#~ msgid "Support"
+#~ msgstr "Wsparcie"
+
+#~ msgid "Docs"
+#~ msgstr "Docs"
+
+#, php-format
+#~ msgid ""
+#~ "Can not enable WP Desk Debug log! Cannot create directory %s or this "
+#~ "directory is not writeable!"
+#~ msgstr ""
+#~ "Nie można włączyć WP Desk Debug log! Nie można utworzyć katalogu %s lub "
+#~ "brak uprawnień do zapisu w tym katalogu!"
+
+#, php-format
+#~ msgid "Can not enable WP Desk Debug log! Cannot create file %s!"
+#~ msgstr ""
+#~ "Nie można włączyć WP Desk Debug log! Nie można utworzyć katalogu %s lub "
+#~ "brak uprawnień do zapisu w tym katalogu!"
+
+#~ msgid "Emergency"
+#~ msgstr "Wypadek"
+
+#~ msgid "Alert"
+#~ msgstr "Ostrzeżenie"
+
+#~ msgid "Critical"
+#~ msgstr "Błąd krytyczny"
+
+#~ msgid "Error"
+#~ msgstr "Błąd"
+
+#~ msgid "Warning"
+#~ msgstr "Ostrzeżenie"
+
+#~ msgid "Notice"
+#~ msgstr "Notyfikacja"
+
+#~ msgid "Info"
+#~ msgstr "Informacja"
+
+#~ msgid "Save Changes"
+#~ msgstr "Zapisz zmiany"
+
+#~ msgid "API Key Activation"
+#~ msgstr "Aktywacja subskrypcji"
+
+#~ msgid "API Key Status"
+#~ msgstr "Status subskrypcji"
+
+#~ msgid "API Subscription Key"
+#~ msgstr "Klucz API"
+
+#~ msgid "API Subscription email"
+#~ msgstr "E-mail API"
+
+#~ msgid "API Key Deactivation"
+#~ msgstr "Dezaktywacja subskrypcji"
+
+#~ msgid "Deactivate API Key"
+#~ msgstr "Dezaktywuj klucz API"
+
+#~ msgid "Plugin activated. "
+#~ msgstr "Wtyczka została włączona. "
+
+#~ msgid "Connection failed to the API Key server. Try again later."
+#~ msgstr "Nie udało się połączyć z serwerem subskrypcji. Spróbuj później."
+
+#~ msgid ""
+#~ "The subscription could not be deactivated. Use the Subscription "
+#~ "Deactivation tab to manually deactivate the subscription before "
+#~ "activating a new subscription."
+#~ msgstr ""
+#~ "Subskrypcja nie może być dezaktywowana. Użyj ręcznej dezaktywacji "
+#~ "subskrypcji przed aktywacją nowej subskrypcji."
+
+#~ msgid "Plugin subscription deactivated. "
+#~ msgstr "Subskrypcja wtyczki dezaktywowana. "
+
+#~ msgid "Deactivates an API Key so it can be used on another blog."
+#~ msgstr ""
+#~ "Dezaktywuje klucz API, dzięki czemu może być użyty na innej stronie."
+
+#~ msgid "Subscription Activation"
+#~ msgstr "Aktywacja subskrypcji"
+
+#~ msgid "Subscription Deactivation"
+#~ msgstr "Dezaktywacja subskrypcji"
+
+#, fuzzy, php-format
+#~| msgid ""
+#~| " <a target='_blank' href='%s'>Enter a valid subscription key for "
+#~| "automatic updates.</a>,"
+#~ msgid ""
+#~ " <a target='_blank' href='%s'>Enter a valid subscription key for "
+#~ "automatic updates.</a>"
+#~ msgstr ""
+#~ " <a target='_blank' href='%s'>Uzupełnij dane subskrypcji, żeby uruchomić "
+#~ "automatyczne aktualizacje.</a>,"
+
+#, php-format
+#~ msgid ""
+#~ "The %s%s%s API Key has not been activated, so you won't be supported and "
+#~ "your plugin won't be updated! %sClick here%s to activate the API key and "
+#~ "the plugin."
+#~ msgstr ""
+#~ "Klucz API wtyczki %s%s%s nie został aktywowany, więc nie otrzymasz "
+#~ "wsparcia i aktualizacji wtyczki. %sKliknij tutaj%s, aby aktywować klucz "
+#~ "API wtyczki."
+
+#, php-format
+#~ msgid ""
+#~ "<b>Warning!</b> You're blocking external requests which means you won't "
+#~ "be able to get %s updates. Please add %s to %s."
+#~ msgstr ""
+#~ "<b>Uwaga!</b> Blokujesz zewnętrzne połączenia i nie będziesz otrzymywać "
+#~ "aktualizacji wtyczki: %s. Proszę dodaj host %s do %s."
+
+#, php-format
+#~ msgid ""
+#~ "The API key for %s has expired. You can reactivate or purchase a API key "
+#~ "from your account <a href=\"%s\" target=\"_blank\">dashboard</a>."
+#~ msgstr ""
+#~ "Subskrypcja na wtyczkę: %s wygasła. Aby odnowić subskrypcję, <a href=\"%s"
+#~ "\" target=\"_blank\">kliknij tutaj</a>."
+
+#, php-format
+#~ msgid ""
+#~ "The subscription for %s is on-hold. You can reactivate the subscription "
+#~ "from your account <a href=\"%s\" target=\"_blank\">dashboard</a>."
+#~ msgstr ""
+#~ "Subskrypcja na wtyczkę: %s jest wstrzymana. Możesz zarządzać "
+#~ "subskrypcjami <a href=\"%s\" target=\"_blank\">tutaj</a>."
+
+#, php-format
+#~ msgid ""
+#~ "The subscription for %s has been cancelled. You can renew the "
+#~ "subscription from your account <a href=\"%s\" target=\"_blank"
+#~ "\">dashboard</a>. A new API key will be emailed to you after your order "
+#~ "has been completed."
+#~ msgstr ""
+#~ "Subskrypcja na wtyczkę: %s została anulowana. Aby odnowić subskrypcję, <a "
+#~ "href=\"%s\" target=\"_blank\">kliknij tutaj</a>."
+
+#, php-format
+#~ msgid ""
+#~ "The subscription for %s has expired. You can reactivate the subscription "
+#~ "from your account <a href=\"%s\" target=\"_blank\">dashboard</a>."
+#~ msgstr ""
+#~ "Subskrypcja na wtyczkę: %s wygasła. Aby odnowić subskrypcję, <a href=\"%s"
+#~ "\" target=\"_blank\">kliknij tutaj</a>."
+
+#, php-format
+#~ msgid ""
+#~ "The subscription for %s has been suspended. You can reactivate the "
+#~ "subscription from your account <a href=\"%s\" target=\"_blank"
+#~ "\">dashboard</a>."
+#~ msgstr ""
+#~ "Subskrypcja na wtyczkę: %s została zawieszona. Aby odnowić subskrypcję, "
+#~ "<a href=\"%s\" target=\"_blank\">kliknij tutaj</a>."
+
+#, php-format
+#~ msgid ""
+#~ "The subscription for %s is still pending. You can check on the status of "
+#~ "the subscription from your account <a href=\"%s\" target=\"_blank"
+#~ "\">dashboard</a>."
+#~ msgstr ""
+#~ "Subskrypcja na wtyczkę: %s jest przetwarzana. Aby sprawdzić status "
+#~ "subskrypcji, <a href=\"%s\" target=\"_blank\">kliknij tutaj</a>."
+
+#, php-format
+#~ msgid ""
+#~ "The subscription for %s has been placed in the trash and will be deleted "
+#~ "soon. You can purchase a new subscription from your account <a href=\"%s"
+#~ "\" target=\"_blank\">dashboard</a>."
+#~ msgstr ""
+#~ "Subskrypcja na wtyczkę: %s została anulowana. Aby odnowić subskrypcję, <a "
+#~ "href=\"%s\" target=\"_blank\">kliknij tutaj</a>."
+
+#, php-format
+#~ msgid ""
+#~ "A subscription for %s could not be found. You can purchase a subscription "
+#~ "from your account <a href=\"%s\" target=\"_blank\">dashboard</a>."
+#~ msgstr ""
+#~ "Subskrypcja na wtyczkę: %s nie została znaleziona. Możesz zarządzać "
+#~ "subskrypcjami <a href=\"%s\" target=\"_blank\">tutaj</a>."
+
+#, php-format
+#~ msgid ""
+#~ "A API key for %s could not be found. Maybe you forgot to enter a API key "
+#~ "when setting up %s, or the key was deactivated in your account. You can "
+#~ "reactivate or purchase a subscription key from your account <a href=\"%s"
+#~ "\" target=\"_blank\">dashboard</a>."
+#~ msgstr ""
+#~ "Subskrypcja na wtyczkę: %s nie została znaleziona. Być może zapomniałeś "
+#~ "wpisać klucza API przy aktywacji: %s lub klucz został dezaktywowany. "
+#~ "Możesz zarządzać subskrypcjami <a href=\"%s\" target=\"_blank\">tutaj</a>."
+
+#, php-format
+#~ msgid ""
+#~ "Download permission for %s has been revoked possibly due to a API key or "
+#~ "subscription expiring. You can reactivate or purchase a API key from your "
+#~ "account <a href=\"%s\" target=\"_blank\">dashboard</a>."
+#~ msgstr ""
+#~ "Możliwość pobrania wtyczki: %s została zablokowana. Aby odnowić "
+#~ "subskrypcję, <a href=\"%s\" target=\"_blank\">kliknij tutaj</a>."
+
+#, php-format
+#~ msgid ""
+#~ "%s has not been activated. Go to the settings page and enter the API key "
+#~ "and subscription email to activate %s."
+#~ msgstr ""
+#~ "Wtyczka %s nie została aktywowana. Przejdź do ustawień i wpisz klucz API "
+#~ "i e-mail, aby aktywować %s."
+
+#, php-format
+#~ msgid ""
+#~ "You changed the subscription for %s, so you will need to enter your new "
+#~ "API Key in the settings page. The API Key should have arrived in your "
+#~ "email inbox, if not you can get it by logging into your account <a href="
+#~ "\"%s\" target=\"_blank\">dashboard</a>."
+#~ msgstr ""
+#~ "Subskrypcja na wtyczkę: %s została zmieniona. Możesz zarządzać "
+#~ "subskrypcjami <a href=\"%s\" target=\"_blank\">tutaj</a>."
+
+#~ msgid ""
+#~ "Connection failed to the Subscription Key API server. Try again later."
+#~ msgstr "Nie udało się połączyć z serwerem subskrypcji. Spróbuj później."
+
+#~ msgid "No WP Desk plugins found."
+#~ msgstr "Nie znaleziono wtyczek WP Desk."
+
+#~ msgid "Plugin"
+#~ msgstr "Wtyczka"
+
+#~ msgid "Subscription Status"
+#~ msgstr "Status subskrypcji"
+
+#~ msgid "Subscription Data"
+#~ msgstr "Dane subskrypcji"
+
+#~ msgid "Deactivated"
+#~ msgstr "Nieaktywna"
+
+#~ msgid "Activated"
+#~ msgstr "Aktywna"
+
+#~ msgid "Key:"
+#~ msgstr "Klucz:"
+
+#~ msgid "Email:"
+#~ msgstr "E-mail:"
+
+#~ msgid "Activate"
+#~ msgstr "Aktywuj"
+
+#~ msgid "Deactivate"
+#~ msgstr "Dezaktywuj"
+
+#~ msgid "WP Desk Subscriptions"
+#~ msgstr "Subskrypcje WP Desk"
+
+#, php-format
+#~ msgid ""
+#~ "Get your subscription keys <a href=\"%s\" target=\"_blank\">here</a>. You "
+#~ "can activate/deactivate API keys <strong>unlimited times on different "
+#~ "domains</strong> as long as you have an active subscription."
+#~ msgstr ""
+#~ "Pobierz klucze API <a href=\"%s\" target=\"_blank\">tutaj</a>. Możesz "
+#~ "aktywować i dezaktywować klucze API <strong>dowolną ilość razy na różnych "
+#~ "domenach</strong>, pod warunkiem, że subskrypcja jest aktywna."
+
+#~ msgid "Subscriptions"
+#~ msgstr "Subskrypcje"
+
+#, php-format
+#~ msgid "Hey %s,"
+#~ msgstr "Cześć %s,"
+
+#~ msgid ""
+#~ "Please help us improve our plugins! If you opt-in, we will collect some "
+#~ "non-sensitive data and usage information anonymously. If you skip this, "
+#~ "that's okay! All plugins will work just fine."
+#~ msgstr ""
+#~ "Proszę pomóż nam udoskonalić nasze wtyczki! Jeśli się zgodzisz, będziemy "
+#~ "zbierać niewrażliwe dane użytkowania anonimowo. Jeśli pominiesz ten krok, "
+#~ "w porządku! Wszystkie wtyczki będą działać."
+
+#~ msgid "Allow & Continue &rarr;"
+#~ msgstr "Pozwól i kontynuuj &rarr;"
+
+#~ msgid "Skip"
+#~ msgstr "Pomiń"
+
+#~ msgid "What permissions are being granted?"
+#~ msgstr "Jakie uprawnienia są przyznawane?"
+
+#~ msgid "Your Site Overview"
+#~ msgstr "Dane witryny"
+
+#~ msgid "WP version, PHP info"
+#~ msgstr "Wersja WP, PHP info"
+
+#~ msgid "Plugin Usage"
+#~ msgstr "Wtyczka"
+
+#~ msgid "Current settings and usage information of WP Desk plugins"
+#~ msgstr "Ustawienia i informacje o użytkowaniu wtyczek WP Desk"
+
+#~ msgid "Your Store Overview"
+#~ msgstr "Informacje o Twoim sklepie"
+
+#~ msgid "Anonymized and non-sensitive store usage information"
+#~ msgstr "Tylko niewrażliwe i anonimowe dane o użytkowaniu sklepu"
+
+#~ msgid "Find out more &raquo;"
+#~ msgstr "Dowiedz się więcej &raquo;"
+
+#, php-format
+#~ msgid "You are deactivating %s plugin."
+#~ msgstr "Dezaktywowałeś wtyczkę %s."
+
+#~ msgid ""
+#~ " If you have a moment, please let us know why you are deactivating plugin "
+#~ "(anonymous feedback):"
+#~ msgstr ""
+#~ " Jeśli masz chwilę, prosimy daj nam znać dlaczego dezaktywowałeś wtyczkę "
+#~ "(opinia jest anonimowa):"
+
+#~ msgid "The plugin suddenly stopped working"
+#~ msgstr "Wtyczka nagle przestała działać"
+
+#~ msgid "The plugin broke my site"
+#~ msgstr "Wtyczka zepsuła stronę"
+
+#~ msgid "I found a better plugin"
+#~ msgstr "Lepsza wtyczka została znaleziona"
+
+#~ msgid "What's the plugin's name?"
+#~ msgstr "Jaka jest nazwa wtyczki?"
+
+#~ msgid "I only needed the plugin for a short period"
+#~ msgstr "Wtyczka była potrzebna tylko na krótko"
+
+#~ msgid "I no longer need the plugin"
+#~ msgstr "Nie potrzebuję już tej wtyczki"
+
+#~ msgid "It's a temporary deactivation. I'm just debugging an issue."
+#~ msgstr "Deaktywacja jest tymczasowa. Debuguję problem."
+
+#~ msgid "Other"
+#~ msgstr "Inne"
+
+#~ msgid "Kindly tell us the reason so we can improve"
+#~ msgstr "Napisz nam powód, abyśmy mogli się poprawić"
+
+#~ msgid "Cancel"
+#~ msgstr "Cofnij"
+
+#~ msgid "Skip &amp; Deactivate"
+#~ msgstr "Pomiń i deaktywuj"
+
+#~ msgid "Submit &amp; Deactivate"
+#~ msgstr "Wyślij i deaktywuj"
+
+#~ msgid ""
+#~ "We need your help to improve <strong>WP Desk plugins</strong>, so they "
+#~ "are more useful for you and the rest of <strong>30,000+ users</strong>. "
+#~ "By collecting data on how you use our plugins, you will help us a lot. We "
+#~ "will not collect any sensitive data, so you can feel safe."
+#~ msgstr ""
+#~ "Potrzebujemy Twojej pomocy, aby dalej rozwijać <strong>wtyczki WP Desk</"
+#~ "strong> i były one jeszcze bardziej pomocne dla Ciebie i pozostałych "
+#~ "ponad <strong>30 000 użytkowników</strong>. Zbierając dane na temat tego "
+#~ "jak korzystasz z naszych wtyczek bardzo nam w tym pomożesz. Nie będziemy "
+#~ "zbierać żadnych danych wrażliwych, więc możesz czuć się bezpiecznie."
+
+#~ msgid "Allow"
+#~ msgstr "Pozwól"
+
+#~ msgid ""
+#~ "You successfully opted out of collecting usage data by WP Desk. If you "
+#~ "change your mind, you can always opt in later in the plugin's quick links."
+#~ msgstr ""
+#~ "Twoja rezygnacja ze zbierania danych użytkowania wtyczek przez WP Desk "
+#~ "została przyjęta. Jeśli zmienisz zdanie, zawsze możesz włączyć zgodę "
+#~ "poprzez szybkie linki w menu wtyczek."
+
+#, php-format
+#~ msgid "Invalid response from WP Desk server: %s"
+#~ msgstr "Błędna odpowiedź serwera Wp Desk: %s"
+
+#, php-format
+#~ msgid "%sInvalid response: <pre>%s</pre>"
+#~ msgstr "%sBłędna odpowiedź: <pre>%s</pre>"
+
+#~ msgid "WP Desk"
+#~ msgstr "WP Desk"
+
+#~ msgid "Refresh"
+#~ msgstr "Odśwież"
+
+#~ msgid "Error: "
+#~ msgstr "Błąd: "
+
+#~ msgid ""
+#~ "To diagnose problem we suggest run this commands from command line on "
+#~ "your server:"
+#~ msgstr ""
+#~ "Aby zdiagnozować problem, proponujemy uruchomić to polecenie z wiersza "
+#~ "poleceń na serwerze:"
+
+#~ msgid "Download plugin"
+#~ msgstr "Pobierz wtyczkę"
+
+#~ msgid "Activate plugin"
+#~ msgstr "Włącz wtyczkę"
+
+#~ msgid "Activate key"
+#~ msgstr "Aktywuj subskrypcję"
+
+#~ msgid "Configure"
+#~ msgstr "Skonfiguruj"
+
+#~ msgid "Update plugin"
+#~ msgstr "Aktualizuj wtyczkę"
+
+#~ msgid "Plugin license deactivated. "
+#~ msgstr "Licencja wtyczki dezaktywowana. "
+
+#~ msgid "Licenses"
+#~ msgstr "Licencje"
+
+#~ msgid "API License Key"
+#~ msgstr "Klucz licencyjny"
+
+#~ msgid "License Status"
+#~ msgstr "Status licencji"
+
+#~ msgid "License Data"
+#~ msgstr "Dane licencji"
+
+#~ msgid "WP Desk Licenses"
+#~ msgstr "Licencje WP Desk"
+
+#~ msgid "WP Desk Helper"
+#~ msgstr "WP Desk Helper"
+
+#~ msgid "https://www.wpdesk.net/"
+#~ msgstr "https://www.wpdesk.pl/"
+
+#~ msgid "Enables WP Desk plugin activation and updates."
+#~ msgstr "Umożliwia aktywację i aktualizacje wtyczek WP Desk."
+
+#~ msgid ""
+#~ "An Unexpected HTTP Error occurred during the API request.</p> <p><a href="
+#~ "\"?\" onclick=\"document.location.reload(); return false;\">Try again</a>"
+#~ msgstr ""
+#~ "Wystąpił nieoczekiwany błąd HTTP podczas zapytania API.</p> <p><a href=\"?"
+#~ "\" onclick=\"document.location.reload(); return false;\">Spróbuj "
+#~ "ponownie</a>"
+
+#~ msgid "An unknown error occurred"
+#~ msgstr "Wystąpił nieznany błąd"
+
+#~ msgid "Ustawienia"
+#~ msgstr "Ustawienia"
+
+#~ msgid "Dokumentacja"
+#~ msgstr "Dokumentacja"
+
+#~ msgid "Wsparcie"
+#~ msgstr "Wsparcie"
+
+#~ msgid "Get your license keys <a href=\"%s\" target=\"_blank\">here</a>."
+#~ msgstr ""
+#~ "Pobierz klucze licencyjne do aktywacji wtyczek <a href=\"%s\" target="
+#~ "\"_blank\">tutaj</a>."
+
+#~ msgid "Documentation"
+#~ msgstr "Dokumentacja"
+
+#~ msgid "http://www.wpdesk.net/"
+#~ msgstr "https://www.wpdesk.pl/"
+
+#~ msgid "http://www.wpdesk.pl/"
+#~ msgstr "http://www.wpdesk.pl/"
+
+#~ msgid "WP Desk Debug Log is enabled. %sPlease disable it after testing.%s"
+#~ msgstr "WP Desk Debug Log jest włączony. %sWyłącz go po testach.%s"
+
+#, fuzzy
+#~ msgid "Writes error log to file %s."
+#~ msgstr "Zapisuje log błędów do %s."
+
+#~ msgid "http://www.wpdesk.pl/sklep/wpdesk-helper/"
+#~ msgstr "http://www.wpdesk.pl/sklep/wpdesk-helper/"
+
+#~ msgid "Product"
+#~ msgstr "Produkt"
diff --git a/phpunit-integration.xml b/phpunit-integration.xml
new file mode 100644
index 0000000..a79673c
--- /dev/null
+++ b/phpunit-integration.xml
@@ -0,0 +1,32 @@
+<phpunit bootstrap="tests/integration/bootstrap.php"
+         backupGlobals="false"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         strict="true"
+     >
+    <testsuites>
+        <testsuite>
+            <directory prefix="Test" suffix=".php">./tests/integration</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory suffix=".php">./src</directory>
+        </whitelist>
+    </filter>
+
+    <logging>
+        <log type="junit" target="build-coverage/report.junit.xml"/>
+        <log type="coverage-html" target="build-coverage/coverage" charset="UTF-8" yui="true" highlight="true"/>
+        <log type="coverage-text" target="build-coverage/coverage.txt"/>
+        <log type="coverage-clover" target="build-coverage/clover.xml"/>
+    </logging>
+
+    <php>
+        <env name="WP_DEVELOP_DIR" value="/tmp/wordpress-develop"/>
+        <env name="WC_DEVELOP_DIR" value="/tmp/woocommerce"/>
+    </php>
+
+</phpunit>
\ No newline at end of file
diff --git a/phpunit-unit.xml b/phpunit-unit.xml
new file mode 100644
index 0000000..31e5c9f
--- /dev/null
+++ b/phpunit-unit.xml
@@ -0,0 +1,21 @@
+<phpunit bootstrap="tests/unit/bootstrap.php">
+    <testsuites>
+        <testsuite>
+            <directory prefix="Test" suffix=".php">./tests/unit/</directory>
+        </testsuite>
+    </testsuites>
+
+    <filter>
+        <whitelist>
+            <directory suffix=".php">src</directory>
+        </whitelist>
+    </filter>
+
+    <logging>
+        <log type="junit" target="build-coverage/report.junit.xml"/>
+        <log type="coverage-html" target="build-coverage/coverage" charset="UTF-8" yui="true" highlight="true"/>
+        <log type="coverage-text" target="build-coverage/coverage.txt"/>
+        <log type="coverage-clover" target="build-coverage/clover.xml"/>
+    </logging>
+
+</phpunit>
diff --git a/src/RatingPetitionNotice.php b/src/RatingPetitionNotice.php
new file mode 100644
index 0000000..4c96bf4
--- /dev/null
+++ b/src/RatingPetitionNotice.php
@@ -0,0 +1,205 @@
+<?php
+/**
+ * Repository rating.
+ *
+ * @package Flexible Shipping Fedex
+ */
+
+namespace WPDesk\RepositoryRating;
+
+use WPDesk\PluginBuilder\Plugin\Hookable;
+
+/**
+ * Can display rating notices based on watcher time time.
+ */
+class RatingPetitionNotice implements Hookable {
+
+	const CLOSE_TEMPORARY_NOTICE = 'close-temporary-notice';
+
+	const NOTICES_OFFSET = 1209600; // Two weeks in seconds.
+
+	/**
+	 * First notice start time.
+	 *
+	 * @var string
+	 */
+	private $first_notice_start_time;
+
+	/**
+	 * Second notice start time.
+	 *
+	 * @var string
+	 */
+	private $second_notice_start_time;
+
+	/**
+	 * Unique namespace for id/option generation
+	 *
+	 * @var string
+	 */
+	private $namespace;
+
+	/**
+	 * Plugin name in notice content.
+	 *
+	 * @var string
+	 */
+	private $plugin_name;
+
+	/**
+	 * RatingPetitionNotice constructor.
+	 *
+	 * @param TimeWatcher $method_watcher Watcher to decide when should first notice be displayed.
+	 * @param string      $namespace Unique namespace for id/option generation.
+	 * @param string      $plugin_name Plugin name in notice content.
+	 */
+	public function __construct( TimeWatcher $method_watcher, $namespace, $plugin_name ) {
+		$this->namespace   = $namespace;
+		$this->plugin_name = $plugin_name;
+
+		$this->second_notice_start_time = get_option( $this->prepare_notice_start_time_option_name(), '' );
+		$this->first_notice_start_time  = '';
+
+		if ( '' === $this->second_notice_start_time && '' !== $method_watcher->get_creation_time() ) {
+			$this->first_notice_start_time = gmdate( 'Y-m-d H:i:s', strtotime( $method_watcher->get_creation_time() ) + self::NOTICES_OFFSET );
+		}
+	}
+
+	/**
+	 * Return option name for second notice timer.
+	 *
+	 * @return string
+	 */
+	private function prepare_notice_start_time_option_name() {
+		return $this->namespace . '_second_notice_time';
+	}
+
+	/**
+	 * Init hooks (actions and filters).
+	 */
+	public function hooks() {
+		add_action( 'admin_notices', array( $this, 'maybe_show_first_notice' ) );
+		add_action( 'admin_notices', array( $this, 'maybe_show_second_notice' ) );
+		add_action(
+			'wpdesk_notice_dismissed_notice',
+			array( $this, 'maybe_start_second_notice_on_dismiss_first_notice' ),
+			10,
+			2
+		);
+	}
+
+	/**
+	 * Maybe reset counter.
+	 *
+	 * @param string $notice_name .
+	 * @param string $source .
+	 */
+	public function maybe_start_second_notice_on_dismiss_first_notice( $notice_name, $source = null ) {
+		if ( $this->prepare_notice_name( 1 ) === $notice_name && ( empty( $source ) || self::CLOSE_TEMPORARY_NOTICE === $source ) ) {
+			update_option( $this->prepare_notice_start_time_option_name(), gmdate( 'Y-m-d H:i:s', intval( current_time( 'timestamp' ) ) + self::NOTICES_OFFSET ) );
+		}
+	}
+
+	/**
+	 * Returns notice name for Notice class.
+	 *
+	 * @param int $notice_number Notice number as there are two notices.
+	 *
+	 * @return string
+	 */
+	private function prepare_notice_name( $notice_number ) {
+		return $this->namespace . '_rating_' . $notice_number;
+	}
+
+	/**
+	 * Maybe show first notice.
+	 */
+	public function maybe_show_first_notice() {
+		if ( $this->should_display_notice() ) {
+			if ( '' !== $this->first_notice_start_time && current_time( 'mysql' ) > $this->first_notice_start_time ) {
+				$this->show_notice( $this->prepare_notice_name( 1 ) );
+			}
+		}
+	}
+
+	/**
+	 * Should display notice.
+	 *
+	 * @return bool
+	 */
+	private function should_display_notice() {
+		$current_screen     = get_current_screen();
+		$display_on_screens = [ 'shop_order', 'edit-shop_order', 'woocommerce_page_wc-settings' ];
+		if ( ! empty( $current_screen ) && in_array( $current_screen->id, $display_on_screens, true ) ) {
+			return true;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Show notice.
+	 *
+	 * @param string $notice_name .
+	 */
+	private function show_notice( $notice_name ) {
+		new \WPDesk\Notice\PermanentDismissibleNotice(
+			$this->get_notice_content(),
+			$notice_name,
+			\WPDesk\Notice\Notice::NOTICE_TYPE_INFO
+		);
+	}
+
+	/**
+	 * Get notice content.
+	 *
+	 * @return string
+	 */
+	private function get_notice_content() {
+		// Translators: plugin name.
+		$content  = sprintf( __( 'Awesome, you\'ve been using %s for more than 2 weeks. Could you please do me a BIG favor and give it a 5-star rating on WordPress? ~ Peter', 'wpdesk-rating-petition' ), $this->plugin_name );
+		$content .= '<br/>';
+		$content .= implode( ' | ', $this->action_links() );
+
+		return $content;
+	}
+
+	/**
+	 * Action links
+	 *
+	 * @return array
+	 */
+	protected function action_links() {
+		$actions[] = sprintf( // phpcs:ignore
+			// Translators: link.
+			__( '%1$sOk, you deserved it%2$s', 'wpdesk-rating-petition' ),
+			'<a class="fs-response-deserved" target="_blank" href="' . esc_url( 'https://wordpress.org/plugins/wpdesk-rating-petition/#reviews' ) . '">',
+			'</a>'
+		);
+		$actions[] = sprintf(
+			// Translators: link.
+			__( '%1$sNope, maybe later%2$s', 'wpdesk-rating-petition' ),
+			'<a class="fs-response-close-temporary-notice notice-dismiss-link" data-source="' . self::CLOSE_TEMPORARY_NOTICE . '" href="#">',
+			'</a>'
+		);
+		$actions[] = sprintf(
+			// Translators: link.
+			__( '%1$sI already did%2$s', 'wpdesk-rating-petition' ),
+			'<a class="fs-response-already-did notice-dismiss-link" data-source="already-did" href="#">',
+			'</a>'
+		);
+
+		return $actions;
+	}
+
+	/**
+	 * Maybe show second notice.
+	 */
+	public function maybe_show_second_notice() {
+		if ( $this->should_display_notice() ) {
+			if ( ( '' !== $this->second_notice_start_time ) && ( current_time( 'mysql' ) > $this->second_notice_start_time ) ) {
+				$this->show_notice( $this->prepare_notice_name( 2 ) );
+			}
+		}
+	}
+}
diff --git a/src/TimeWatcher.php b/src/TimeWatcher.php
new file mode 100644
index 0000000..7a28fe2
--- /dev/null
+++ b/src/TimeWatcher.php
@@ -0,0 +1,22 @@
+<?php
+/**
+ * TimeWatcher interface.
+ *
+ * @package WPDesk\RepositoryRating
+ */
+
+namespace WPDesk\RepositoryRating;
+
+/**
+ * Simple interface for tracking creation time of certaing things.
+ *
+ * @package WPDesk\RepositoryRating
+ */
+interface TimeWatcher {
+	/**
+	 * Return creation time of watched item.
+	 *
+	 * @return string Returns date string. Can also return '' when no creation time is found.
+	 */
+	public function get_creation_time();
+}
diff --git a/src/TimeWatcher/ShippingMethodGlobalSettingsWatcher.php b/src/TimeWatcher/ShippingMethodGlobalSettingsWatcher.php
new file mode 100644
index 0000000..4e1e0d8
--- /dev/null
+++ b/src/TimeWatcher/ShippingMethodGlobalSettingsWatcher.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * Shipping method watcher.
+ *
+ * @package Flexible Shipping Fedex
+ */
+
+namespace WPDesk\RepositoryRating\TimeWatcher;
+
+use WPDesk\PluginBuilder\Plugin\Hookable;
+use WPDesk\RepositoryRating\TimeWatcher;
+
+/**
+ * Can watch shipping method creation.
+ */
+class ShippingMethodGlobalSettingsWatcher implements Hookable, TimeWatcher {
+	const NO_CREATION_TIME = '';
+
+	/**
+	 * Unique id of shipping method used in WC_Shipping_Method::id.
+	 *
+	 * @var string
+	 */
+	private $shipping_method_unique_id;
+
+	/**
+	 * ShippingMethodGlobalSettingsWatcher constructor.
+	 *
+	 * @param string $shipping_method_unique_id Unique id of shipping method used in WC_Shipping_Method::id.
+	 */
+	public function __construct( $shipping_method_unique_id ) {
+		$this->shipping_method_unique_id = $shipping_method_unique_id;
+	}
+
+	/**
+	 * Init hooks (actions and filters).
+	 */
+	public function hooks() {
+		add_action(
+			"woocommerce_update_options_shipping_{$this->shipping_method_unique_id}", function () {
+				$this->watch_saved_settings();
+			}
+		);
+	}
+
+	/**
+	 * Watch saved settings and save time when first saved.
+	 */
+	private function watch_saved_settings() {
+		if ( ! $this->is_creation_time_saved() ) {
+			$this->update_creation_time();
+		}
+	}
+
+	/**
+	 * Is creation time saved or it's first time?.
+	 *
+	 * @return bool
+	 */
+	private function is_creation_time_saved() {
+		return $this->get_creation_time() !== self::NO_CREATION_TIME;
+	}
+
+	/**
+	 * Set ccreation time to now.
+	 */
+	private function update_creation_time() {
+		update_option( $this->prepare_creation_time_option_name(), current_time( 'mysql' ) );
+	}
+
+	/**
+	 * Option name to save time in db.
+	 *
+	 * @return string
+	 */
+	private function prepare_creation_time_option_name() {
+		return $this->shipping_method_unique_id . '_repository_creation_time';
+	}
+
+	/**
+	 * Get first method creation time.
+	 *
+	 * @return string
+	 */
+	public function get_creation_time() {
+		return get_option( $this->prepare_creation_time_option_name(), self::NO_CREATION_TIME );
+	}
+}
diff --git a/src/TimeWatcher/ShippingMethodInstanceWatcher.php b/src/TimeWatcher/ShippingMethodInstanceWatcher.php
new file mode 100644
index 0000000..764297a
--- /dev/null
+++ b/src/TimeWatcher/ShippingMethodInstanceWatcher.php
@@ -0,0 +1,174 @@
+<?php
+/**
+ * Shipping method watcher.
+ *
+ * @package Flexible Shipping Fedex
+ */
+
+namespace WPDesk\RepositoryRating\TimeWatcher;
+
+use WPDesk\PluginBuilder\Plugin\Hookable;
+use WPDesk\RepositoryRating\TimeWatcher;
+
+/**
+ * Can watch shipping method creation.
+ *
+ * @todo This class is not tested yet. Should be used in UPS.
+ */
+class ShippingMethodInstanceWatcher implements Hookable, TimeWatcher {
+	/**
+	 * First method added time.
+	 *
+	 * @var string
+	 */
+	private $first_method_creation_time = '';
+
+	/**
+	 * First method watching.
+	 *
+	 * @var int
+	 */
+	private $first_method_watching = 0;
+
+	/**
+	 * Unique namespace for id/option generation
+	 *
+	 * @var string
+	 */
+	private $namespace;
+
+	/**
+	 * Name of shipping method ::class
+	 *
+	 * @var string
+	 */
+	private $shipping_method_name;
+
+	/**
+	 * Name of WP option with plugin activation time
+	 *
+	 * @var string
+	 */
+	private $plugin_activation_time_option_name;
+
+	/**
+	 * Date in string when functionality started and we can start watching
+	 *
+	 * @var string
+	 */
+	private $zero_date;
+
+	/**
+	 * ShippingMethodInstanceWatcher constructor.
+	 *
+	 * @param string $namespace Unique namespace for id/option generation.
+	 * @param string $plugin_activation_time_option_name Where in options is plugin activation time.
+	 * @param string $zero_date When functionality started and we can start watching.
+	 * @param string $shipping_method_name What method name is watched.
+	 */
+	public function __construct( $namespace, $plugin_activation_time_option_name, $zero_date, $shipping_method_name ) {
+		$this->namespace                          = $namespace;
+		$this->plugin_activation_time_option_name = $plugin_activation_time_option_name;
+		$this->zero_date                          = $zero_date;
+		$this->shipping_method_name               = $shipping_method_name;
+	}
+
+	/**
+	 * Init hooks (actions and filters).
+	 */
+	public function hooks() {
+		add_action( 'admin_init', array( $this, 'maybe_init_watching' ), 10, 3 );
+		add_action( 'woocommerce_shipping_zone_method_added', array( $this, 'watch_added_shipping_method' ), 10, 3 );
+	}
+
+	/**
+	 * Init watching.
+	 */
+	public function maybe_init_watching() {
+		$this->first_method_watching = intval( get_option( $this->prepare_option_name_watching(), 0 ) );
+		if ( 0 === $this->first_method_watching ) {
+			$ups_free_activation_time = get_option( $this->plugin_activation_time_option_name, current_time( 'mysql' ) );
+			if ( strtotime( $ups_free_activation_time ) < strtotime( $this->zero_date ) ) {
+				$this->init_watching_from_existing_shipping_methods();
+			}
+			update_option( $this->prepare_option_name_watching(), 1 );
+			$this->first_method_watching = 1;
+		}
+	}
+
+	/**
+	 * Returns options name for no idea what.
+	 *
+	 * @return string
+	 */
+	private function prepare_option_name_watching() {
+		return $this->namespace . '_method_watching';
+	}
+
+	/**
+	 * First time init watching.
+	 * Sets first time to current time when Fedex shipping method already exists.
+	 */
+	private function init_watching_from_existing_shipping_methods() {
+		$shipping_zones = \WC_Shipping_Zones::get_zones();
+		/** @var \WC_Shipping_Zone $shipping_zone */ // phpcs:ignore
+		foreach ( $shipping_zones as $shipping_zone_data ) {
+			$shipping_zone = \WC_Shipping_Zones::get_zone( $shipping_zone_data['id'] );
+			if ( $shipping_zone && $shipping_zone instanceof \WC_Shipping_Zone ) {
+				$shipping_methods = $shipping_zone->get_shipping_methods();
+				foreach ( $shipping_methods as $shipping_method ) {
+					if ( $shipping_method instanceof $this->shipping_method_name ) {
+						update_option( $this->prepare_option_name_method_created(), current_time( 'mysql' ) );
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Returns options name for info when shipping method was created.
+	 *
+	 * @return string
+	 */
+	private function prepare_option_name_method_created() {
+		return $this->namespace . '_method_created';
+	}
+
+	/**
+	 * Watch added shipping method.
+	 * Set first time to current time when first UPS shipping method created.
+	 *
+	 * @param int    $instance_id .
+	 * @param string $type .
+	 * @param int    $zone_id .
+	 */
+	public function watch_added_shipping_method( $instance_id, $type, $zone_id ) {
+		$this->first_method_creation_time = get_option( $this->prepare_option_name_method_created(), '' );
+		if ( $this->namespace === $type ) {
+			if ( '' === $this->first_method_creation_time ) {
+				$this->first_method_creation_time = (string) current_time( 'mysql' );
+				update_option( $this->prepare_option_name_method_created(), $this->first_method_creation_time );
+			}
+		}
+	}
+
+	/**
+	 * Get first method creation time.
+	 *
+	 * @return string
+	 */
+	public function get_creation_time() {
+		$this->first_method_creation_time = get_option( $this->prepare_option_name_method_created(), '' );
+
+		return $this->first_method_creation_time;
+	}
+
+	/**
+	 * Returns date when functionality started and we can start watching.
+	 *
+	 * @return string
+	 */
+	private function get_watcher_zero_date() {
+		return $this->zero_date;
+	}
+}
diff --git a/tests/integration/bootstrap.php b/tests/integration/bootstrap.php
new file mode 100644
index 0000000..3fd9d70
--- /dev/null
+++ b/tests/integration/bootstrap.php
@@ -0,0 +1,26 @@
+<?php
+
+ini_set('error_reporting', E_ALL); // or error_reporting(E_ALL);
+ini_set('display_errors', '1');
+ini_set('display_startup_errors', '1');
+
+require_once __DIR__ . '/../../vendor/autoload.php';
+
+// disable xdebug backtrace
+if ( function_exists( 'xdebug_disable' ) ) {
+	xdebug_disable();
+}
+
+if ( getenv( 'PLUGIN_PATH' ) !== false ) {
+	define( 'PLUGIN_PATH', getenv( 'PLUGIN_PATH' ) );
+} else {
+	define( 'PLUGIN_PATH', __DIR__ . DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR );
+}
+
+require_once( getenv( 'WP_DEVELOP_DIR' ) . '/tests/phpunit/includes/functions.php' );
+
+tests_add_filter( 'muplugins_loaded', function () {
+}, 100 );
+
+putenv('WP_TESTS_DIR=' . getenv( 'WP_DEVELOP_DIR' ) . '/tests/phpunit');
+require_once( getenv( 'WC_DEVELOP_DIR' ) . '/tests/bootstrap.php' );
\ No newline at end of file
diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php
new file mode 100644
index 0000000..575b33b
--- /dev/null
+++ b/tests/unit/bootstrap.php
@@ -0,0 +1,6 @@
+<?php
+/**
+ * PHPUnit bootstrap file
+ */
+
+require_once __DIR__ . '/../../vendor/autoload.php';
-- 
GitLab