diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7147bd4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,53 @@ +name: create-release-artifact + +on: + release: + types: [published] + +jobs: + create-release-artifact: + name: Creating release artifact + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v2 + + - name: cached dependencies + uses: actions/cache@v2 + id: cached-dependencies + with: + path: ./vendor + # the key will change if composer.lock changes + key: ${{ runner.os }}-dependencies-${{ hashFiles('**/composer.lock') }} + + - name: install dependencies + uses: php-actions/composer@v6 + with: + command: run build:prod + + - name: version + id: version + run: echo "::set-output name=version::$(jq -r '.version' ./composer.json)" + + - name: build artifacts + run: composer run package + + - name: upload regular artifact + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ./tmp/tawk-oc3-${{ steps.version.outputs.version }}.zip + asset_name: tawk-oc3-${{ steps.version.outputs.version }}.zip + asset_content_type: application/zip + + - name: upload ocmod artifact + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ./tmp/tawk-oc3-${{ steps.version.outputs.version }}.ocmod.zip + asset_name: tawk-oc3-${{ steps.version.outputs.version }}.ocmod.zip + asset_content_type: application/zip diff --git a/.gitignore b/.gitignore index 5f456e0..d83132f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ upload/ *.zip -docker/bin +/vendor +/docker/bin +/tmp diff --git a/README.md b/README.md index 7997965..7d8d6b3 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,11 @@ This section describes how to install the plugin and get it working. > If you aren’t using the English language for the OpenCart admin area, copy the language file into the correct language folder. If you are not using the default OpenCart theme, be sure to paste tawkto.twig in the correct theme folder. * admin/controller/extension/module/tawkto.php -> ``/admin/controller/extension/module/ +* admin/controller/extension/module/tawkto/ -> ``/admin/controller/extension/module/ * admin/language/en-gb/extension/module/tawkto.php -> ``/admin/language/``/extension/module/ * admin/view/image/tawkto/ -> ``/admin/view/image/ * admin/view/template/extension/module/tawkto.twig -> ``/admin/view/template/extension/module/ +* admin/view/template/extension/module/tawkto/ -> ``/admin/view/template/extension/module/ * catalog/controller/extension/module/tawkto.php -> ``/catalog/controller/extension/module/tawkto.php * catalog/view/theme/default/template/extension/module/tawkto.twig -> ``/catalog/view/theme/``/template/extension/module/tawkto.twig @@ -44,6 +46,9 @@ Visit our [Help Center](https://help.tawk.to/) for answers to FAQs ## Changelog +### 2.2.0 +* Enhanced pattern matching for filtering pages to show/hide widgets. + ### 2.1.1 * Added function for widget selection iframe to auto resize. * Provided platform identifier to widget selection iframe. diff --git a/admin/controller/extension/module/tawkto.php b/admin/controller/extension/module/tawkto.php index 62c5a2b..929910d 100644 --- a/admin/controller/extension/module/tawkto.php +++ b/admin/controller/extension/module/tawkto.php @@ -6,6 +6,10 @@ * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html */ +define('PLUGIN_VERSION', '2.2.0'); + +require_once dirname(__FILE__) . '/tawkto/upgrades/manager.php'; + class ControllerExtensionModuleTawkto extends Controller { private $error = array(); @@ -29,6 +33,18 @@ public function index() { $this->document->setTitle($this->language->get('heading_title')); $this->load->model('setting/module'); + // upgrade manager + $dependencies = array( + 'model_setting' => $this->model_setting_setting, + 'model_store' => $this->model_setting_store + ); + $options = array( + 'version' => PLUGIN_VERSION, + 'version_var_name' => 'module_tawkto_version' + ); + $upgrade_manager = new TawkToUpgradeManager($dependencies, $options); + $upgrade_manager->start(); + $data = $this->setupIndexTexts(); $data['module_tawkto_status'] = $this->config->get('module_tawkto_status'); @@ -399,7 +415,10 @@ private function getBaseUrl() { public function install() { $this->setup(); - $this->model_setting_setting->editSetting('module_tawkto', array("module_tawkto_status" => 1)); + $this->model_setting_setting->editSetting('module_tawkto', array( + 'module_tawkto_status' => 1, + 'module_tawkto_version' => PLUGIN_VERSION + )); $this->enableAllLayouts(); } diff --git a/admin/view/template/extension/module/tawkto.twig b/admin/view/template/extension/module/tawkto.twig index a9f01af..9e9b2b3 100644 --- a/admin/view/template/extension/module/tawkto.twig +++ b/admin/view/template/extension/module/tawkto.twig @@ -61,6 +61,47 @@ margin-top: 1rem; } } + +/* Tooltip */ +.tawk-tooltip { + position: relative; + display: inline; + color: #03a84e; +} + +.tawk-tooltip .tawk-tooltiptext { + visibility: hidden; + background-color: #545454; + color: #fff; + text-align: center; + padding: 0.5rem; + max-width: 300px; + border-radius: 0.5rem; + font-size: 1.2rem; + line-height: 1; + + /* Position the tooltip text - see examples below! */ + position: absolute; + z-index: 1000; + top: 12px; +} + +.tawk-tooltip .tawk-tooltiptext::before { + content: ""; + display: block; + width: 0; + height: 0; + position: absolute; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-bottom: 5px solid #545454; + top: -5px; + left: 5px; +} + +.tawk-tooltip:hover .tawk-tooltiptext { + visibility: visible; +} {{ column_left }}
@@ -190,13 +260,42 @@ {% else %} + id="show_oncustom" cols="30" rows="10">{% for page in show_whitelist %}{{page ~ '\r\n'}}{% endfor %} {% endif %}
-

- Add URLs to pages in which you would like to show the widget.
- Put each URL in a new line. -

+
+ Add URLs/paths to pages in which you would like to show the widget.
+ Put each URL/path in a new line. Paths should have a leading '/'. +
+
+ Examples of accepted path patterns +
    +
  • *
  • +
  • */to/somewhere
  • +
  • /*/to/somewhere
  • +
  • /path/*/somewhere
  • +
  • /path/*/lead/*/somewhere
  • +
  • /path/*/*/somewhere
  • +
  • /path/to/*
  • +
  • /path/to/*/
  • +
  • */to/*/page
  • +
  • /*/to/*/page
  • +
  • /path/*/other/*
  • +
  • /path/*/other/*/
  • +
  • http://www.example.com/
  • +
  • http://www.example.com/*
  • +
  • http://www.example.com/*/to/somewhere
  • +
  • http://www.example.com/path/*/somewhere
  • +
  • http://www.example.com/path/*/lead/*/somewhere
  • +
  • http://www.example.com/path/*/*/somewhere
  • +
  • http://www.example.com/path/to/*
  • +
  • http://www.example.com/path/to/*/
  • +
  • http://www.example.com/*/to/*/page
  • +
  • http://www.example.com/path/*/other/*
  • +
  • http://www.example.com/path/*/other/*/
  • +
+
+


diff --git a/build-package.sh b/build-package.sh index 8eca3dd..41f2b6e 100755 --- a/build-package.sh +++ b/build-package.sh @@ -1,26 +1,26 @@ #!/bin/sh -if [ -z "$1" ] - then - echo "Release version wasn't specified"; - return; -fi +release_version=$(jq -r '.version' ./composer.json); -release_version=$1; +echo "Creating temporary directory" +rm -rf ./tmp; +mkdir -p ./tmp/upload; +mkdir -p ./tmp/admin/controller/extension/module/tawkto; +mkdir -p ./tmp/catalog/controller/extension/module/tawkto; -echo "Creating temporary upload directory" -rm -rf ./upload -mkdir ./upload -echo "Copying files to upload directory" -cp -r admin ./upload/ -cp -r catalog ./upload/ +echo "Copying files" +cp README.md ./tmp; +cp -r admin ./tmp; +cp -r catalog ./tmp; +cp -r upgrades ./tmp/admin/controller/extension/module/tawkto; +cp -r vendor ./tmp/admin/controller/extension/module/tawkto; +cp -r vendor ./tmp/catalog/controller/extension/module/tawkto; +cp -r ./tmp/admin ./tmp/upload; +cp -r ./tmp/catalog ./tmp/upload; echo "Creating opencart 3 zip files" -zip -9 -rq tawk-oc3-$release_version.ocmod.zip upload README.md -zip -9 -rq tawk-oc3-$release_version.zip admin catalog README.md - -echo "Cleaning up" -rm -rf ./upload +$(cd ./tmp && zip -9 -rq tawk-oc3-$release_version.ocmod.zip upload README.md); +$(cd ./tmp && zip -9 -rq tawk-oc3-$release_version.zip admin catalog README.md); echo "Done!" diff --git a/catalog/controller/extension/module/tawkto.php b/catalog/controller/extension/module/tawkto.php index 3b7c79e..8214eed 100644 --- a/catalog/controller/extension/module/tawkto.php +++ b/catalog/controller/extension/module/tawkto.php @@ -6,6 +6,12 @@ * @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html */ +define('PATTERN_MATCHING_UPDATE_VERSION', '2.2.0'); + +require_once dirname(__FILE__) . '/tawkto/vendor/autoload.php'; + +use Tawk\Modules\UrlPatternMatcher; + class ControllerExtensionModuleTawkto extends Controller { //we include embed script only once even if more than one layout is displayed @@ -20,7 +26,16 @@ public function index() { } self::$displayed = true; - $widget = $this->getWidget(); + $this->load->model('setting/setting'); + + // get current plugin version in db + $tawk_settings = $this->model_setting_setting->getSetting('module_tawkto'); // this gets the default store settings since that's where the version is stored. + $plugin_version_in_db = ''; + if (isset($tawk_settings['module_tawkto_version'])) { + $plugin_version_in_db = $tawk_settings['module_tawkto_version']; + } + + $widget = $this->getWidget($plugin_version_in_db); $settings = json_decode($this->getVisibilitySettings()); if($widget === null) { @@ -73,9 +88,7 @@ public function getVisitor() return null; } - private function getWidget() { - - $this->load->model('setting/setting'); + private function getWidget($plugin_version_in_db) { $store_id = $this->config->get('config_store_id'); $settings = $this->model_setting_setting->getSetting('module_tawkto', $store_id); $language_id = $this->config->get('config_language_id'); @@ -120,29 +133,10 @@ private function getWidget() { // custom pages $show_pages = json_decode($visibility->show_oncustom); $show = false; - $current_page = (string) trim($current_page); - foreach ($show_pages as $slug) { - $slug = trim($slug); - if (empty($slug)) { - continue; - } - - /*use this when testing on a Linux/Win*/ - // we need to add htmlspecialchars due to slashes added when saving to database - // $slug = (string) htmlspecialchars($slug); - $slug = (string) urldecode($slug); - $slug = str_ireplace($this->config->get('config_url'), '', $slug); - - /*use this when testing on a Mac*/ - // we need to add htmlspecialchars due to slashes added when saving to database - // $slug = (string) urldecode($slug); - $slug = addslashes($slug); - if (stripos($current_page, $slug)!==false || $slug == $current_page) { - $show = true; - break; - } + if ($this->matchPatterns($current_page, $show_pages, $plugin_version_in_db)) { + $show = true; } // category page @@ -165,7 +159,6 @@ private function getWidget() { } } - if (!$show) { return; } @@ -173,29 +166,10 @@ private function getWidget() { } else { $show = true; $hide_pages = json_decode($visibility->hide_oncustom); - $current_page = (string) trim($current_page); - foreach ($hide_pages as $slug) { - $slug = trim($slug); - if (empty($slug)) { - continue; - } - - /*use this when testing on a Linux/Win*/ - // we need to add htmlspecialchars due to slashes added when saving to database - // $slug = (string) htmlspecialchars($slug); - $slug = (string) urldecode($slug); - $slug = str_ireplace($this->config->get('config_url'), '', $slug); - - /*use this when testing on a Mac*/ - // we need to add htmlspecialchars due to slashes added when saving to database - // $slug = (string) urldecode($slug); - $slug = addslashes($slug); - if (stripos($current_page, $slug)!==false || $slug == $current_page) { - $show = false; - break; - } + if ($this->matchPatterns($current_page, $hide_pages, $plugin_version_in_db)) { + $show = false; } if (!$show) { @@ -208,7 +182,6 @@ private function getWidget() { } private function getVisibilitySettings() { - $this->load->model('setting/setting'); $store_id = $this->config->get('config_store_id'); $settings = $this->model_setting_setting->getSetting('module_tawkto', $store_id); @@ -230,4 +203,27 @@ private function getLayoutId() { return $this->model_design_layout->getLayout($route); } + + private function matchPatterns($current_page, $pages, $plugin_version) { + if (version_compare($plugin_version, PATTERN_MATCHING_UPDATE_VERSION) >= 0) { + return UrlPatternMatcher::match($current_page, $pages); + } + + // handle backwards compatibility + foreach ($pages as $slug) { + $slug = trim($slug); + if (empty($slug)) { + continue; + } + $slug = (string) urldecode($slug); + $slug = str_ireplace($this->config->get('config_url'), '', $slug); + $slug = addslashes($slug); + + if (stripos($current_page, $slug)!==false || $slug == $current_page) { + return true; + } + } + + return false; + } } diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..38e5769 --- /dev/null +++ b/composer.json @@ -0,0 +1,22 @@ +{ + "name": "tawk/tawk-opencart3", + "description": "tawk.to extension module for Opencart 3.", + "version": "2.2.0", + "type": "project", + "license": "GPL3", + "require": { + "php": ">=5.6.0", + "tawk/url-utils": "^2.0" + }, + "repositories": { + "tawk-url-utils": { + "type": "vcs", + "url": "https://github.com/tawk/tawk-url-utils.git" + } + }, + "scripts": { + "release" : "composer run build:prod && composer run package", + "build:prod" : "composer install --no-dev", + "package" : "./build-package.sh" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..1a34aa1 --- /dev/null +++ b/composer.lock @@ -0,0 +1,70 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "1d8bba63aaa1617d73e19c534026652f", + "packages": [ + { + "name": "tawk/url-utils", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/tawk/tawk-url-utils.git", + "reference": "73c166333707d893b0160fa9b5eae7aa8fbfa03c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tawk/tawk-url-utils/zipball/73c166333707d893b0160fa9b5eae7aa8fbfa03c", + "reference": "73c166333707d893b0160fa9b5eae7aa8fbfa03c", + "shasum": "" + }, + "require-dev": { + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^9.5", + "squizlabs/php_codesniffer": "^3.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tawk\\": "lib", + "Tawk\\Tests\\": "tests" + } + }, + "scripts": { + "post-install-cmd": [ + "([ $COMPOSER_DEV_MODE -eq 0 ] || vendor/bin/phpcs --config-set installed_paths vendor/phpcompatibility/php-compatibility)" + ], + "post-update-cmd": [ + "([ $COMPOSER_DEV_MODE -eq 0 ] || vendor/bin/phpcs --config-set installed_paths vendor/phpcompatibility/php-compatibility)" + ], + "test": [ + "phpunit" + ], + "lint": [ + "phpcs -p -s -v --runtime-set ignore_warnings_on_exit true ." + ], + "lint:fix": [ + "phpcbf -p -s -v .; err=$?; if [ $err -eq 1 ]; then exit 0; else exit $err; fi;" + ] + }, + "support": { + "source": "https://github.com/tawk/tawk-url-utils/tree/2.0.1", + "issues": "https://github.com/tawk/tawk-url-utils/issues" + }, + "time": "2022-01-28T11:14:45+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.6.0" + }, + "platform-dev": [], + "plugin-api-version": "2.1.0" +} diff --git a/docker/build.sh b/docker/build.sh index 00bc572..77a74d6 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -12,9 +12,11 @@ fi echo "Creating module folder"; mkdir -p $module_dir; +echo "Installing dependencies" +composer run release --working-dir=$build_dir/.. + echo "Copying files to module folder"; -cp -r $build_dir/../admin $module_dir -cp -r $build_dir/../catalog $module_dir +cp -r $build_dir/../tmp/* $module_dir echo "Done building module folder"; diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index f07e9e3..0c7ed69 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -11,6 +11,7 @@ services: - ALLOW_EMPTY_PASSWORD=yes web: + container_name: opencart-30 depends_on: - db build: . @@ -20,6 +21,8 @@ services: environment: - MARIADB_HOST=db - MARIADB_PORT_NUMBER=3306 + - OPENCART_DATABASE_HOST=db + - OPENCART_DATABASE_PORT_NUMBER=3306 - OPENCART_DATABASE_USER=bn_opencart - OPENCART_DATABASE_NAME=bitnami_opencart - OPENCART_USERNAME=admin diff --git a/upgrades/base.php b/upgrades/base.php new file mode 100644 index 0000000..1e67aa3 --- /dev/null +++ b/upgrades/base.php @@ -0,0 +1,17 @@ +model_setting = $dependencies['model_setting']; + $this->model_store = $dependencies['model_store']; + $this->upgrades = array( + TawkToUpgradeVersion220::get_version() => TawkToUpgradeVersion220::class, + ); + + $this->version_var_name = $options['version_var_name']; + $this->current_setting = $this->model_setting->getSetting('module_tawkto'); + $this->curr_ver = $options['version']; + $this->prev_ver = ''; + + if (isset($this->current_setting[$this->version_var_name])) { + $this->prev_ver = $this->current_setting[$this->version_var_name]; + } + } + + public function start() { + if (!empty($this->prev_ver) && version_compare($this->prev_ver, $this->curr_ver) >= 0) { + // do not do anything. + return; + } + + if (empty($this->prev_var)) { + // initialize tawkto_version setting + $this->current_setting[$this->version_var_name] = ''; + $this->model_setting->editSetting('module_tawkto', $this->current_setting); + } + + // special case: we've never set the version before. + // All plugins prior to the current version needs the upgrade. + if (version_compare($this->prev_ver, $this->curr_ver) < 0) { + // are there upgrade steps depending on how out-of-date? + foreach ($this->upgrades as $upgrade_ver => $upgrade) { + // only run upgrades if upgrade version is lower than + // and equal to the current version. + if (version_compare($upgrade_ver, $this->curr_ver) <= 0) { + $this->do_upgrade($upgrade_ver); + } + + $this->model_setting->editSettingValue('module_tawkto', $this->version_var_name, $upgrade_ver); + } + } + + } + + protected function get_upgrade_class($version) { + if (false === array_key_exists($version, $this->upgrades)) { + return null; + } + + return $this->upgrades[ $version ]; + } + + protected function do_upgrade($version) { + $upgrade_class = $this->get_upgrade_class($version); + + if (true === is_null($upgrade_class)) { + return; + } + + $upgrade_class::upgrade($this->model_setting, $this->model_store); + } +} diff --git a/upgrades/version.220.php b/upgrades/version.220.php new file mode 100644 index 0000000..a9a3d33 --- /dev/null +++ b/upgrades/version.220.php @@ -0,0 +1,127 @@ +getSetting('module_tawkto', $store_id); + + if (!isset($store_settings['module_tawkto_visibility'])) { + continue; + } + + $visibility = json_decode($store_settings['module_tawkto_visibility'], true); + if (!empty($visibility['hide_oncustom'])) { + $visibility['hide_oncustom'] = self::process_patterns(json_decode($visibility['hide_oncustom'])); + } + + if (!empty($visibility['show_oncustom'])) { + $visibility['show_oncustom'] = self::process_patterns(json_decode($visibility['show_oncustom'])); + } + + $store_settings['module_tawkto_visibility'] = json_encode($visibility); + + $model_setting->editSetting('module_tawkto', $store_settings, $store_id); + } + } + + protected static function process_patterns($pattern_list) { + $wildcard = PathHelper::get_wildcard(); + + if (self::check_pattern_list_has_wildcard($pattern_list, $wildcard)) { + return json_encode($pattern_list); + } + + $new_pattern_list = []; + $added_patterns = []; + + foreach ($pattern_list as $pattern) { + if (empty($pattern)) { + continue; + } + + $pattern = ltrim($pattern, PHP_EOL); + $pattern = trim($pattern); + + if (strpos($pattern, 'http://') !== 0 && + strpos($pattern, 'https://') !== 0 && + strpos($pattern, '/') !== 0 + ) { + // Check if the first part of the string is a host. + // If not, add a leading / so that the pattern + // matcher treats is as a path. + $firstPatternChunk = explode('/', $pattern)[0]; + if (self::check_valid_host($firstPatternChunk) === false) { + $pattern = '/' . $pattern; + } + } + + $new_pattern_list[] = $pattern; + $newPattern = $pattern . '/' . $wildcard; + if (in_array($newPattern, $pattern_list, true)) { + continue; + } + + if (true === isset($added_patterns[$newPattern])) { + continue; + } + + $new_pattern_list[] = $newPattern; + $added_patterns[$newPattern] = true; + } + + // EOL for display purposes + return json_encode($new_pattern_list); + } + + protected static function check_pattern_list_has_wildcard($patternList, $wildcard) { + foreach ($patternList as $pattern) { + if (strpos($pattern, $wildcard) > -1) { + return true; + } + } + + return false; + } + + protected static function check_valid_host($host) { + // contains port + if (strpos($host, ':') < 0) { + return true; + } + + // is localhost + if (strpos($host, 'localhost') === 0) { + return true; + } + + // gotten from https://forums.digitalpoint.com/threads/what-will-be-preg_match-for-domain-names.1953314/#post-15036873 + // but updated the ending regex part to include numbers so it also matches IPs. + $host_check_regex = '/^[a-zA-Z0-9]*((-|\.)?[a-zA-Z0-9])*\.([a-zA-Z0-9]{1,4})$/'; + + return preg_match($host_check_regex, $host) > 0; + } + + protected static function get_store_ids($model_store) { + $retrieved_stores = $model_store->getStores(); + $stores = array(0); + + foreach ($retrieved_stores as $store) { + array_push($stores, $store['store_id']); + }; + + return $stores; + } +}