first commit

This commit is contained in:
2025-01-06 20:47:25 +01:00
commit 3bdbd78c2f
25591 changed files with 3586440 additions and 0 deletions

7
modules/smartsupp/vendor/autoload.php vendored Normal file
View File

@@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit8dab3547849c97a9298bf7cb711c8367::getLoader();

View File

@@ -0,0 +1,445 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see http://www.php-fig.org/psr/psr-0/
* @see http://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', $this->prefixesPsr0);
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

View File

@@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
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.

View File

@@ -0,0 +1,12 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'SmartsuppAuthApi' => $baseDir . '/classes/Auth/Api.php',
'SmartsuppAuthCurlRequest' => $baseDir . '/classes/Auth/Request/CurlRequest.php',
'SmartsuppAuthHttpRequest' => $baseDir . '/classes/Auth/Request/HttpRequest.php',
);

View File

@@ -0,0 +1,10 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Smartsupp' => array($vendorDir . '/smartsupp/chat-code-generator/src', $vendorDir . '/smartsupp/php-partner-client/src'),
);

View File

@@ -0,0 +1,10 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Smartsupp\\LiveChat\\' => array($baseDir . '/src'),
);

View File

@@ -0,0 +1,52 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit8dab3547849c97a9298bf7cb711c8367
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit8dab3547849c97a9298bf7cb711c8367', 'loadClassLoader'), true, false);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit8dab3547849c97a9298bf7cb711c8367', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require_once __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit8dab3547849c97a9298bf7cb711c8367::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(false);
return $loader;
}
}

View File

@@ -0,0 +1,50 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInit8dab3547849c97a9298bf7cb711c8367
{
public static $prefixLengthsPsr4 = array (
'S' =>
array (
'Smartsupp\\LiveChat\\' => 19,
),
);
public static $prefixDirsPsr4 = array (
'Smartsupp\\LiveChat\\' =>
array (
0 => __DIR__ . '/../..' . '/src',
),
);
public static $prefixesPsr0 = array (
'S' =>
array (
'Smartsupp' =>
array (
0 => __DIR__ . '/..' . '/smartsupp/chat-code-generator/src',
1 => __DIR__ . '/..' . '/smartsupp/php-partner-client/src',
),
),
);
public static $classMap = array (
'SmartsuppAuthApi' => __DIR__ . '/../..' . '/classes/Auth/Api.php',
'SmartsuppAuthCurlRequest' => __DIR__ . '/../..' . '/classes/Auth/Request/CurlRequest.php',
'SmartsuppAuthHttpRequest' => __DIR__ . '/../..' . '/classes/Auth/Request/HttpRequest.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit8dab3547849c97a9298bf7cb711c8367::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit8dab3547849c97a9298bf7cb711c8367::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInit8dab3547849c97a9298bf7cb711c8367::$prefixesPsr0;
$loader->classMap = ComposerStaticInit8dab3547849c97a9298bf7cb711c8367::$classMap;
}, null, ClassLoader::class);
}
}

View File

@@ -0,0 +1,91 @@
[
{
"name": "smartsupp/chat-code-generator",
"version": "1.0.1",
"version_normalized": "1.0.1.0",
"source": {
"type": "git",
"url": "https://github.com/smartsupp/chat-code-generator.git",
"reference": "1f63b44aeb90a1cd9e37b260a35b0ccb3377feea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/smartsupp/chat-code-generator/zipball/1f63b44aeb90a1cd9e37b260a35b0ccb3377feea",
"reference": "1f63b44aeb90a1cd9e37b260a35b0ccb3377feea",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "4.7.*"
},
"time": "2018-11-08T15:36:32+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-0": {
"Smartsupp": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"authors": [
{
"name": "Marek Gach",
"role": "lead"
}
],
"description": "This simple PHP class allows you to easily generate Smartsupp.com JS chat code.",
"homepage": "https://www.smartsupp.com/",
"keywords": [
"chat"
]
},
{
"name": "smartsupp/php-partner-client",
"version": "1.1",
"version_normalized": "1.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/smartsupp/php-auth-client.git",
"reference": "caa587a89ae551f1356f083baf29d31567468020"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/smartsupp/php-auth-client/zipball/caa587a89ae551f1356f083baf29d31567468020",
"reference": "caa587a89ae551f1356f083baf29d31567468020",
"shasum": ""
},
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "4.8.*"
},
"time": "2019-09-30T15:08:09+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"Smartsupp": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"authors": [
{
"name": "Marek Gach",
"role": "lead"
}
],
"description": "API client allows to register and login (obtain API key) from Smartsupp partner API.",
"homepage": "https://www.smartsupp.com/",
"keywords": [
"chat"
]
}
]

View File

@@ -0,0 +1,4 @@
.idea/*
/vendor/
composer.lock
/build/

View File

@@ -0,0 +1,22 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- hhvm
matrix:
allow_failures:
- php: hhvm
before_script:
- composer require satooshi/php-coveralls:1.0.1 squizlabs/php_codesniffer
- composer install
script:
- vendor/bin/phpcs src/ -p --standard=PSR2 --report=summary
- mkdir -p build/logs
- vendor/bin/phpunit --configuration phpunit.xml --coverage-text
after_success:
- sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php vendor/bin/coveralls -v; fi;'

View File

@@ -0,0 +1,33 @@
[![Build Status](https://travis-ci.org/smartsupp/chat-code-generator.svg)](https://travis-ci.org/smartsupp/chat-code-generator)
[![Coverage Status](https://coveralls.io/repos/smartsupp/chat-code-generator/badge.svg?branch=master&service=github)](https://coveralls.io/github/smartsupp/chat-code-generator?branch=master)
# Smartsupp chat code generator
This is simple PHP class for Smartsupp chat API which helps you to generate chat JavaScript code.
* https://www.smartsupp.com/
* [More info about Smartsupp CHAT API](https://developers.smartsupp.com/chat/configuration) This is "Get started" doc for chat API.
* [More info about Smartsupp CHAT API - Overview](https://developers.smartsupp.com/chat/overview) This is full documentation for chat API. Note, that not all properties are possible to be set using this class.
## Get Started
Here is an example on how to use it:
```php
$chat = new Smartsupp\ChatGenerator;
$chat->setKey('XYZ123456');
$chat->disableSendEmailTranscript();
$chat->enableRating('advanced', true);
$chat->setBoxPosition('left', 'side', 20, 120);
$chat->setName('Johny Depp');
$chat->setEmail('johny@depp.com');
$chat->setVariable('orderTotal', 'Total orders', 150);
$chat->setVariable('lastOrder', 'Last ordered', '2015-07-09');
$chat->setGoogleAnalytics('UA-123456');
$data = $chat->render();
```
## Copyright
Copyright (c) 2016 Smartsupp.com, s.r.o.

View File

@@ -0,0 +1,34 @@
{
"name": "smartsupp/chat-code-generator",
"description": "This simple PHP class allows you to easily generate Smartsupp.com JS chat code.",
"type": "library",
"keywords": [
"chat"
],
"homepage": "https://www.smartsupp.com/",
"authors": [
{
"name": "Marek Gach",
"role": "lead"
}
],
"support": {
"issues": "https://github.com/smartsupp/chat-code-generator/issues",
"wiki": "https://github.com/smartsupp/chat-code-generator/wiki",
"source": "https://github.com/smartsupp/chat-code-generator"
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-0": {"Smartsupp": "src/"}
},
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "4.7.*"
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="Smartsupp JS code generator">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
</phpunit>

View File

@@ -0,0 +1,495 @@
<?php
namespace Smartsupp;
/**
* Generates widget chat code for Smartsupp.com
*
* PHP version >=5.3
*
* @package Smartsupp
* @author Marek Gach <gach@kurzor.net>
* @copyright since 2015 SmartSupp.com
* @version Git: $Id$
* @link https://github.com/smartsupp/chat-code-generator
* @since File available since Release 0.1
*/
class ChatGenerator
{
/**
* @var array Values which are allowed for ratingType param.
*/
protected $allowed_rating_types = array('advanced', 'simple');
/**
* @var array Values which are allowed for alignX param.
*/
protected $allowed_align_x = array('right', 'left');
/**
* @var array Values which are allowed for alignY param.
*/
protected $allowed_align_y = array('side', 'bottom');
/**
* @var array Values which are allowed for widget param.
*/
protected $allowed_widget = array('button', 'widget');
/**
* @var null|string Your unique chat code. Can be obtained after registration.
*/
protected $key = null;
/**
* @var null|string By default chat conversation is terminated when visitor opens a sub-domain on your website.
*/
protected $cookie_domain = null;
/**
* @var string Chat language.
*/
protected $language = 'en';
/**
* @var string Chat charset defaults to utf-8.
*/
protected $charset = 'utf-8';
/**
* @var null|string Email (basic information).
*/
protected $email = null;
/**
* @var null|string Customer name (basic information).
*/
protected $name = null;
/**
* @var null|array contain additional user information.
*/
protected $variables = null;
/**
* @var bool When the visitor ends the conversation a confirmation window is displayed. This flag defaults to true
* and can be changed.
*/
protected $send_email_transcript = true;
/**
* @var bool Indicate if rating is enabled.
*/
protected $rating_enabled = false;
/**
* @var string Rating type.
*/
protected $rating_type = 'simple';
/**
* @var bool Set if rating comment is enambled.
*/
protected $rating_comment = false;
/**
* @var string Chat X align.
*/
protected $align_x = null;
/**
* @var string Chat Y align.
*/
protected $align_y = null;
/**
* @var int Chat X offset.
*/
protected $offset_x = null;
/**
* @var int Chat Y offset.
*/
protected $offset_y = null;
/**
* @var string Widget type.
*/
protected $widget = null;
/**
* @var null|string Google analytics key value.
*/
protected $ga_key = null;
/**
* @var null|array Google analytics additional options.
*/
protected $ga_options = null;
/**
* @var bool
*/
protected $hide_widget = false;
/**
* @var null|string plugin platform
*/
protected $platform = null;
public function __construct($key = null)
{
$this->key = $key;
}
/**
* Set platform - serves as internal information for Smartsupp to identify which CMS and version is used.
*
* @param string $platform
*/
public function setPlatform($platform)
{
$this->platform = $platform;
}
/**
* Set chat language. Also is checking if language is one of allowed values.
*
* @param string $language
* @throws \Exception when parameter value is incorrect
*/
public function setLanguage($language)
{
$this->language = $language;
}
/**
* Set the charset. Check also if charset is allowed and valid value.
*
* @param string $charset
*/
public function setCharset($charset)
{
$this->charset = $charset;
}
/**
* Allows to set Smartsupp code.
*
* @param string $key Smartsupp chat key.
*/
public function setKey($key)
{
$this->key = $key;
}
/**
* Smartsupp visitor is identified by unique key stored in cookies. By default chat conversation is terminated when
* visitor opens a sub-domain on your website. You should set main domain as cookie domain if you want chat
* conversations uninterrupted across your sub-domains. Insert the cookieDomain parameter in your chat code on main
* domain and all sub-domains where you want the chat conversation uninterrupted.
*
* Example: Use value '.your-domain.com' to let chat work also on all sub-domains and main domain.
*
* @param string $cookie_domain
*/
public function setCookieDomain($cookie_domain)
{
$this->cookie_domain = $cookie_domain;
}
/**
* When the visitor ends the conversation a confirmation window is displayed. In this window there is by default a
* button to send a transcript of the chat conversation to email. You can choose not to show this button.
*/
public function disableSendEmailTranscript()
{
$this->send_email_transcript = false;
}
/**
* After visitors ends a chat conversation, he is prompted to rate the conversation. Rating is disabled by default.
* Together with enabling it you can set additional parameters.
*
* @param string $rating_type
* @param boolean|false $rating_comment
* @throws \Exception when parameter value is incorrect
*/
public function enableRating($rating_type = 'simple', $rating_comment = false)
{
if (!in_array($rating_type, $this->allowed_rating_types)) {
throw new \Exception("Rating type $rating_type is not allowed value. You can use only one of values: " .
implode(', ', $this->allowed_rating_types) . ".");
}
$rating_comment = (bool) $rating_comment;
$this->rating_enabled = true;
$this->rating_type = $rating_type;
$this->rating_comment = $rating_comment;
}
/**
* You can send visitor name. So your visitors won't be anonymous and your chat agents will see info about every
* visitor, enabling agents to better focus on VIP visitors and provide customized answers.
*
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* You can send visitor email. So your visitors won't be anonymous and your chat agents will see info about every
* visitor, enabling agents to better focus on VIP visitors and provide customized answers.
*
* @param string $name
*/
public function setEmail($email)
{
$this->email = $email;
}
/**
* Will add additional parameter into Extra user info variables list.
*
* @param $id Parameter ID.
* @param $label Parameter label.
* @param $value Parameter value.
*/
public function setVariable($id, $label, $value)
{
$variable = array('id' => $id, 'label' => $label, 'value' => $value);
$this->variables[] = $variable;
}
/**
* By default the chat box is displayed in bottom right corner of the website. You can change the default position
* along the bottom line or place the chat on right or left side of the website.
*
* @param string $align_x Align to right or left.
* @param string $align_y Align to bottom or side.
* @param int $offset_x Offset from left or right.
* @param int $offset_y Offset from top.
* @throws \Exception When params are not correct.
*/
public function setAlign($align_x = 'right', $align_y = 'bottom', $offset_x = 10, $offset_y = 100)
{
if (!in_array($align_x, $this->allowed_align_x)) {
throw new \Exception("AllignX value $align_x is not allowed value. You can use only one of values: " .
implode(', ', $this->allowed_align_x) . ".");
}
if (!in_array($align_y, $this->allowed_align_y)) {
throw new \Exception("AllignY value $align_y is not allowed value. You can use only one of values: " .
implode(', ', $this->allowed_align_y) . ".");
}
$this->align_x = $align_x;
$this->align_y = $align_y;
$this->offset_x = $offset_x;
$this->offset_y = $offset_y;
}
/**
* We supports two chat-box layouts, widget and button. By default is activated layout widget.
*
* @param string $widget Parameter value.
* @throws \Exception when parameter value is incorrect
*/
public function setWidget($widget = 'widget')
{
if (!in_array($widget, $this->allowed_widget)) {
throw new \Exception("Widget value $widget is not allowed value. You can use only one of values: " .
implode(', ', $this->allowed_widget) . ".");
}
$this->widget = $widget;
}
/**
* Smartsupp is linked with your Google Analytics (GA) automatically. Smartsupp automatically checks your site's
* code for GA property ID and sends data to that account. If you are using Google Tag Manager (GTM) or you don't
* have GA code directly inserted in your site's code for some reason, you have to link your GA account as described
* here.
* If you have sub-domains on your website and you are tracking all sub-domains in one GA account, use the gaOptions
* parameter. You can find more info about gaOptions in Google Analytics documentation
* (@see https://developers.google.com/analytics/devguides/collection/analyticsjs/advanced#customizeTracker).
*
* @param $ga_key Google analytics key.
* @param array|null $ga_options Additional gaOptions.
*/
public function setGoogleAnalytics($ga_key, array $ga_options = null)
{
$this->ga_key = $ga_key;
$this->ga_options = $ga_options;
}
/**
* You can hide chat box on certain pages by setting this variable.
*/
public function hideWidget()
{
$this->hide_widget = true;
}
/**
* Function for javascript variable value escaping.
*
* @param $str string String to encode.
* @return string Encoded string.
*/
public function javascriptEscape($str)
{
$new_str = '';
for ($i = 0; $i < mb_strlen($str); $i++) {
// obtain single character
$char = mb_substr($str, $i, 1);
// if is alphanumeric put directly into string
if (!in_array($char, array("'"))) {
$new_str .= $char;
} else { // else encode as hex
$new_str .= '\\x' . bin2hex($char);
}
}
return $new_str;
}
/**
* Will assemble chat JS code. Class property with name key need to be set before rendering.
*
* @param bool|false $print_out Force to echo JS chat code instead of returning it.
* @return string
* @throws \Exception Will reach exception when key param is not set.
*/
public function render($print_out = false)
{
if (empty($this->key)) {
throw new \Exception("At least KEY param must be set!");
}
$params = array();
$params2 = array();
// set cookie domain if not blank
if ($this->cookie_domain) {
$params[] = "_smartsupp.cookieDomain = '%cookie_domain%';";
}
// If is set to false, turn off. Default value is true.
if (!$this->send_email_transcript) {
$params[] = "_smartsupp.sendEmailTanscript = false;";
}
if ($this->rating_enabled) {
$params[] = "_smartsupp.ratingEnabled = true; // by default false";
$params[] = "_smartsupp.ratingType = '" . $this->rating_type . "'; // by default 'simple'";
$params[] = "_smartsupp.ratingComment = " . ($this->rating_comment? 'true':'false') . "; // default false";
}
if ($this->align_x && $this->align_y && $this->widget) {
$params[] = "_smartsupp.alignX = '" . $this->align_x . "'; // or 'left'";
$params[] = "_smartsupp.alignY = '" . $this->align_y . "'; // by default 'bottom'";
$params[] = "_smartsupp.widget = '" . $this->widget . "'; // by default 'widget'";
}
if ($this->offset_x && $this->offset_y) {
$params[] = "_smartsupp.offsetX = " . (int)$this->offset_x . "; // offset from left / right, default 10";
$params[] = "_smartsupp.offsetY = " . (int)$this->offset_y . "; // offset from top, default 100";
}
if ($this->platform) {
$params[] = "_smartsupp.sitePlatform = '" . self::javascriptEscape($this->platform) . "';";
}
// set detailed visitor's info
// basic info
if ($this->email) {
$params2[] = "smartsupp('email', '" . self::javascriptEscape($this->email) . "');";
}
if ($this->name) {
$params2[] = "smartsupp('name', '" . self::javascriptEscape($this->name) . "');";
}
// extra info
if ($this->variables) {
$options = array();
foreach ($this->variables as $key => $value) {
$options[] = self::javascriptEscape($value['id']) .": {label: '" .
self::javascriptEscape($value['label']) . "', value: '" . self::javascriptEscape($value['value']) .
"'}";
}
$params2[] = "smartsupp('variables', {" .
implode(", ", $options) .
"});";
}
// set GA key and additional GA params
if ($this->ga_key) {
$params[] = "_smartsupp.gaKey = '%ga_key%';";
if (!empty($this->ga_options)) {
$options = array();
foreach ($this->ga_options as $key => $value) {
$options[] = "'" . self::javascriptEscape($key) . "': '" . self::javascriptEscape($value) . "'";
}
$params[] = "_smartsupp.gaOptions = {" . implode(", ", $options) . "};";
}
}
// hide widget if needed
if ($this->hide_widget) {
$params[] = "_smartsupp.hideWidget = true;";
}
// create basic code and append params
$code = "<script type=\"text/javascript\">
var _smartsupp = _smartsupp || {};
_smartsupp.key = '%key%';\n" .
implode("\n", $params) . "\n" .
"document.addEventListener('scroll', smartsupplaunch);
document.addEventListener('mousedown', smartsupplaunch);
document.addEventListener('mousemove', smartsupplaunch);
document.addEventListener('touchstart', smartsupplaunch);
document.addEventListener('keydown', smartsupplaunch);
function smartsupplaunch () {
window.smartsupp||(function(d) {
var s,c,o=smartsupp=function(){ o._.push(arguments)};o._=[];
s=d.getElementsByTagName('script')[0];c=d.createElement('script');
c.type='text/javascript';c.charset='utf-8';c.async=true;
c.src='//www.smartsuppchat.com/loader.js';s.parentNode.insertBefore(c,s);
})(document);
document.removeEventListener('scroll', smartsupplaunch);
document.removeEventListener('mousedown', smartsupplaunch);
document.removeEventListener('mousemove', smartsupplaunch);
document.removeEventListener('touchstart', smartsupplaunch);
document.removeEventListener('keydown', smartsupplaunch);
}
"
. implode("\n", $params2) . "
</script>";
$code = str_replace('%key%', self::javascriptEscape($this->key), $code);
$code = str_replace('%cookie_domain%', self::javascriptEscape($this->cookie_domain), $code);
$code = str_replace('%ga_key%', self::javascriptEscape($this->ga_key), $code);
if ($print_out) {
echo $code;
} else {
return $code;
}
}
}

View File

@@ -0,0 +1,481 @@
<?php
namespace Smartsupp;
/**
* Generates widget chat code for Smartsupp.com
*
* PHP version >=5.3
*
* @package Smartsupp
* @author Marek Gach <gach@kurzor.net>
* @copyright since 2015 SmartSupp.com
* @version Git: $Id$
* @link https://github.com/smartsupp/chat-code-generator
* @since File available since Release 0.1
*/
class ChatGenerator
{
/**
* @var array Values which are allowed for ratingType param.
*/
protected $allowed_rating_types = array('advanced', 'simple');
/**
* @var array Values which are allowed for alignX param.
*/
protected $allowed_align_x = array('right', 'left');
/**
* @var array Values which are allowed for alignY param.
*/
protected $allowed_align_y = array('side', 'bottom');
/**
* @var array Values which are allowed for widget param.
*/
protected $allowed_widget = array('button', 'widget');
/**
* @var null|string Your unique chat code. Can be obtained after registration.
*/
protected $key = null;
/**
* @var null|string By default chat conversation is terminated when visitor opens a sub-domain on your website.
*/
protected $cookie_domain = null;
/**
* @var string Chat language.
*/
protected $language = 'en';
/**
* @var string Chat charset defaults to utf-8.
*/
protected $charset = 'utf-8';
/**
* @var null|string Email (basic information).
*/
protected $email = null;
/**
* @var null|string Customer name (basic information).
*/
protected $name = null;
/**
* @var null|array contain additional user information.
*/
protected $variables = null;
/**
* @var bool When the visitor ends the conversation a confirmation window is displayed. This flag defaults to true
* and can be changed.
*/
protected $send_email_transcript = true;
/**
* @var bool Indicate if rating is enabled.
*/
protected $rating_enabled = false;
/**
* @var string Rating type.
*/
protected $rating_type = 'simple';
/**
* @var bool Set if rating comment is enambled.
*/
protected $rating_comment = false;
/**
* @var string Chat X align.
*/
protected $align_x = null;
/**
* @var string Chat Y align.
*/
protected $align_y = null;
/**
* @var int Chat X offset.
*/
protected $offset_x = null;
/**
* @var int Chat Y offset.
*/
protected $offset_y = null;
/**
* @var string Widget type.
*/
protected $widget = null;
/**
* @var null|string Google analytics key value.
*/
protected $ga_key = null;
/**
* @var null|array Google analytics additional options.
*/
protected $ga_options = null;
/**
* @var bool
*/
protected $hide_widget = false;
/**
* @var null|string plugin platform
*/
protected $platform = null;
public function __construct($key = null)
{
$this->key = $key;
}
/**
* Set platform - serves as internal information for Smartsupp to identify which CMS and version is used.
*
* @param string $platform
*/
public function setPlatform($platform)
{
$this->platform = $platform;
}
/**
* Set chat language. Also is checking if language is one of allowed values.
*
* @param string $language
* @throws \Exception when parameter value is incorrect
*/
public function setLanguage($language)
{
$this->language = $language;
}
/**
* Set the charset. Check also if charset is allowed and valid value.
*
* @param string $charset
*/
public function setCharset($charset)
{
$this->charset = $charset;
}
/**
* Allows to set Smartsupp code.
*
* @param string $key Smartsupp chat key.
*/
public function setKey($key)
{
$this->key = $key;
}
/**
* Smartsupp visitor is identified by unique key stored in cookies. By default chat conversation is terminated when
* visitor opens a sub-domain on your website. You should set main domain as cookie domain if you want chat
* conversations uninterrupted across your sub-domains. Insert the cookieDomain parameter in your chat code on main
* domain and all sub-domains where you want the chat conversation uninterrupted.
*
* Example: Use value '.your-domain.com' to let chat work also on all sub-domains and main domain.
*
* @param string $cookie_domain
*/
public function setCookieDomain($cookie_domain)
{
$this->cookie_domain = $cookie_domain;
}
/**
* When the visitor ends the conversation a confirmation window is displayed. In this window there is by default a
* button to send a transcript of the chat conversation to email. You can choose not to show this button.
*/
public function disableSendEmailTranscript()
{
$this->send_email_transcript = false;
}
/**
* After visitors ends a chat conversation, he is prompted to rate the conversation. Rating is disabled by default.
* Together with enabling it you can set additional parameters.
*
* @param string $rating_type
* @param boolean|false $rating_comment
* @throws \Exception when parameter value is incorrect
*/
public function enableRating($rating_type = 'simple', $rating_comment = false)
{
if (!in_array($rating_type, $this->allowed_rating_types)) {
throw new \Exception("Rating type $rating_type is not allowed value. You can use only one of values: " .
implode(', ', $this->allowed_rating_types) . ".");
}
$rating_comment = (bool) $rating_comment;
$this->rating_enabled = true;
$this->rating_type = $rating_type;
$this->rating_comment = $rating_comment;
}
/**
* You can send visitor name. So your visitors won't be anonymous and your chat agents will see info about every
* visitor, enabling agents to better focus on VIP visitors and provide customized answers.
*
* @param string $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* You can send visitor email. So your visitors won't be anonymous and your chat agents will see info about every
* visitor, enabling agents to better focus on VIP visitors and provide customized answers.
*
* @param string $name
*/
public function setEmail($email)
{
$this->email = $email;
}
/**
* Will add additional parameter into Extra user info variables list.
*
* @param $id Parameter ID.
* @param $label Parameter label.
* @param $value Parameter value.
*/
public function setVariable($id, $label, $value)
{
$variable = array('id' => $id, 'label' => $label, 'value' => $value);
$this->variables[] = $variable;
}
/**
* By default the chat box is displayed in bottom right corner of the website. You can change the default position
* along the bottom line or place the chat on right or left side of the website.
*
* @param string $align_x Align to right or left.
* @param string $align_y Align to bottom or side.
* @param int $offset_x Offset from left or right.
* @param int $offset_y Offset from top.
* @throws \Exception When params are not correct.
*/
public function setAlign($align_x = 'right', $align_y = 'bottom', $offset_x = 10, $offset_y = 100)
{
if (!in_array($align_x, $this->allowed_align_x)) {
throw new \Exception("AllignX value $align_x is not allowed value. You can use only one of values: " .
implode(', ', $this->allowed_align_x) . ".");
}
if (!in_array($align_y, $this->allowed_align_y)) {
throw new \Exception("AllignY value $align_y is not allowed value. You can use only one of values: " .
implode(', ', $this->allowed_align_y) . ".");
}
$this->align_x = $align_x;
$this->align_y = $align_y;
$this->offset_x = $offset_x;
$this->offset_y = $offset_y;
}
/**
* We supports two chat-box layouts, widget and button. By default is activated layout widget.
*
* @param string $widget Parameter value.
* @throws \Exception when parameter value is incorrect
*/
public function setWidget($widget = 'widget')
{
if (!in_array($widget, $this->allowed_widget)) {
throw new \Exception("Widget value $widget is not allowed value. You can use only one of values: " .
implode(', ', $this->allowed_widget) . ".");
}
$this->widget = $widget;
}
/**
* Smartsupp is linked with your Google Analytics (GA) automatically. Smartsupp automatically checks your site's
* code for GA property ID and sends data to that account. If you are using Google Tag Manager (GTM) or you don't
* have GA code directly inserted in your site's code for some reason, you have to link your GA account as described
* here.
* If you have sub-domains on your website and you are tracking all sub-domains in one GA account, use the gaOptions
* parameter. You can find more info about gaOptions in Google Analytics documentation
* (@see https://developers.google.com/analytics/devguides/collection/analyticsjs/advanced#customizeTracker).
*
* @param $ga_key Google analytics key.
* @param array|null $ga_options Additional gaOptions.
*/
public function setGoogleAnalytics($ga_key, array $ga_options = null)
{
$this->ga_key = $ga_key;
$this->ga_options = $ga_options;
}
/**
* You can hide chat box on certain pages by setting this variable.
*/
public function hideWidget()
{
$this->hide_widget = true;
}
/**
* Function for javascript variable value escaping.
*
* @param $str string String to encode.
* @return string Encoded string.
*/
public function javascriptEscape($str)
{
$new_str = '';
for ($i = 0; $i < mb_strlen($str); $i++) {
// obtain single character
$char = mb_substr($str, $i, 1);
// if is alphanumeric put directly into string
if (!in_array($char, array("'"))) {
$new_str .= $char;
} else { // else encode as hex
$new_str .= '\\x' . bin2hex($char);
}
}
return $new_str;
}
/**
* Will assemble chat JS code. Class property with name key need to be set before rendering.
*
* @param bool|false $print_out Force to echo JS chat code instead of returning it.
* @return string
* @throws \Exception Will reach exception when key param is not set.
*/
public function render($print_out = false)
{
if (empty($this->key)) {
throw new \Exception("At least KEY param must be set!");
}
$params = array();
$params2 = array();
// set cookie domain if not blank
if ($this->cookie_domain) {
$params[] = "_smartsupp.cookieDomain = '%cookie_domain%';";
}
// If is set to false, turn off. Default value is true.
if (!$this->send_email_transcript) {
$params[] = "_smartsupp.sendEmailTanscript = false;";
}
if ($this->rating_enabled) {
$params[] = "_smartsupp.ratingEnabled = true; // by default false";
$params[] = "_smartsupp.ratingType = '" . $this->rating_type . "'; // by default 'simple'";
$params[] = "_smartsupp.ratingComment = " . ($this->rating_comment? 'true':'false') . "; // default false";
}
if ($this->align_x && $this->align_y && $this->widget) {
$params[] = "_smartsupp.alignX = '" . $this->align_x . "'; // or 'left'";
$params[] = "_smartsupp.alignY = '" . $this->align_y . "'; // by default 'bottom'";
$params[] = "_smartsupp.widget = '" . $this->widget . "'; // by default 'widget'";
}
if ($this->offset_x && $this->offset_y) {
$params[] = "_smartsupp.offsetX = " . (int)$this->offset_x . "; // offset from left / right, default 10";
$params[] = "_smartsupp.offsetY = " . (int)$this->offset_y . "; // offset from top, default 100";
}
if ($this->platform) {
$params[] = "_smartsupp.sitePlatform = '" . self::javascriptEscape($this->platform) . "';";
}
// set detailed visitor's info
// basic info
if ($this->email) {
$params2[] = "smartsupp('email', '" . self::javascriptEscape($this->email) . "');";
}
if ($this->name) {
$params2[] = "smartsupp('name', '" . self::javascriptEscape($this->name) . "');";
}
// extra info
if ($this->variables) {
$options = array();
foreach ($this->variables as $key => $value) {
$options[] = self::javascriptEscape($value['id']) .": {label: '" .
self::javascriptEscape($value['label']) . "', value: '" . self::javascriptEscape($value['value']) .
"'}";
}
$params2[] = "smartsupp('variables', {" .
implode(", ", $options) .
"});";
}
// set GA key and additional GA params
if ($this->ga_key) {
$params[] = "_smartsupp.gaKey = '%ga_key%';";
if (!empty($this->ga_options)) {
$options = array();
foreach ($this->ga_options as $key => $value) {
$options[] = "'" . self::javascriptEscape($key) . "': '" . self::javascriptEscape($value) . "'";
}
$params[] = "_smartsupp.gaOptions = {" . implode(", ", $options) . "};";
}
}
// hide widget if needed
if ($this->hide_widget) {
$params[] = "_smartsupp.hideWidget = true;";
}
// create basic code and append params
$code = "<script type=\"text/javascript\">
var _smartsupp = _smartsupp || {};
_smartsupp.key = '%key%';\n" .
implode("\n", $params) . "\n" .
"window.smartsupp||(function(d) {
var s,c,o=smartsupp=function(){ o._.push(arguments)};o._=[];
s=d.getElementsByTagName('script')[0];c=d.createElement('script');
c.type='text/javascript';c.charset='utf-8';c.async=true;
c.src='//www.smartsuppchat.com/loader.js';s.parentNode.insertBefore(c,s);
})(document);"
. implode("\n", $params2) . "
</script>";
$code = str_replace('%key%', self::javascriptEscape($this->key), $code);
$code = str_replace('%cookie_domain%', self::javascriptEscape($this->cookie_domain), $code);
$code = str_replace('%ga_key%', self::javascriptEscape($this->ga_key), $code);
if ($print_out) {
echo $code;
} else {
return $code;
}
}
}

View File

@@ -0,0 +1,257 @@
<?php
namespace Smartsupp;
class ChatGeneratorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ChatGenerator
*/
protected $chat;
protected function setUp()
{
parent::setUp();
$this->chat = new ChatGenerator();
}
public function test_javascriptEscape()
{
$ret = $this->chat->javascriptEscape('abc123');
$this->assertEquals('abc123', $ret);
}
public function test_javascriptEscape_someScript()
{
$ret = $this->chat->javascriptEscape("<script>alert('xss')</script>");
$this->assertEquals('<script>alert(\x27xss\x27)</script>', $ret);
}
public function test_javascriptEscape_someSpecialChars()
{
$ret = $this->chat->javascriptEscape("\"'*!@#$%^&*()_+}{:?><.,/`~");
$this->assertEquals('"\x27*!@#$%^&*()_+}{:?><.,/`~', $ret);
}
public function test_hideWidget()
{
$this->chat->hideWidget();
$this->assertTrue(self::getPrivateField($this->chat, 'hide_widget'));
}
public function test_setGoogleAnalytics()
{
$this->chat->setGoogleAnalytics('UA-123456789');
$this->assertEquals('UA-123456789', self::getPrivateField($this->chat, 'ga_key'));
}
public function test_setGoogleAnalytics_withOptions()
{
$options = array('cookieDomain' => 'foo.example.com');
$this->chat->setGoogleAnalytics('UA-123456789', $options);
$this->assertEquals('UA-123456789', self::getPrivateField($this->chat, 'ga_key'));
$this->assertEquals($options, self::getPrivateField($this->chat, 'ga_options'));
}
public function test_widget()
{
$this->chat->setWidget('button');
$this->assertEquals('button', self::getPrivateField($this->chat, 'widget'));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Widget value foo is not allowed value. You can use only one of values: button, widget.
*/
public function test_widget_badParam()
{
$this->chat->setWidget('foo');
}
public function test_setBoxPosition()
{
$this->chat->setAlign('left', 'side', 20, 120);
$this->assertEquals('left', self::getPrivateField($this->chat, 'align_x'));
$this->assertEquals('side', self::getPrivateField($this->chat, 'align_y'));
$this->assertEquals('20', self::getPrivateField($this->chat, 'offset_x'));
$this->assertEquals('120', self::getPrivateField($this->chat, 'offset_y'));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage AllignX value foo is not allowed value. You can use only one of values: right, left.
*/
public function test_setBoxPosition_badParam()
{
$this->chat->setAlign('foo', 'side', 20, 120);
}
/**
* @expectedException \Exception
* @expectedExceptionMessage AllignY value foo is not allowed value. You can use only one of values: side, bottom.
*/
public function test_setBoxPosition_badParam2()
{
$this->chat->setAlign('left', 'foo', 20, 120);
}
public function test_setVariable()
{
$this->chat->setVariable('userId', 'User ID', 123);
$this->chat->setVariable('orderedPrice', 'Ordered Price in Eshop', '128 000');
$this->assertEquals(
array(
array('id' => 'userId', 'label' => 'User ID', 'value' => 123),
array('id' => 'orderedPrice', 'label' => 'Ordered Price in Eshop', 'value' => '128 000')
),
self::getPrivateField($this->chat, 'variables')
);
}
public function test_setUserBasicInformation()
{
$this->chat->setName('Johny Depp');
$this->chat->setEmail('johny@depp.com');
$this->assertEquals('Johny Depp', self::getPrivateField($this->chat, 'name'));
$this->assertEquals('johny@depp.com', self::getPrivateField($this->chat, 'email'));
}
public function test_enableRating()
{
$this->chat->enableRating('advanced', true);
$this->assertTrue(self::getPrivateField($this->chat, 'rating_enabled'));
$this->assertEquals('advanced', self::getPrivateField($this->chat, 'rating_type'));
$this->assertTrue(self::getPrivateField($this->chat, 'rating_comment'));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Rating type foo is not allowed value. You can use only one of values: advanced, simple.
*/
public function test_enableRating_badParam()
{
$this->chat->enableRating('foo');
}
public function test_disableSendEmailTranscript()
{
$this->assertTrue(self::getPrivateField($this->chat, 'send_email_transcript'));
$this->chat->disableSendEmailTranscript();
$this->assertFalse(self::getPrivateField($this->chat, 'send_email_transcript'));
}
public function test_setCookieDomain()
{
$this->assertNull(self::getPrivateField($this->chat, 'cookie_domain'));
$this->chat->setCookieDomain('.foo.bar');
$this->assertEquals('.foo.bar', self::getPrivateField($this->chat, 'cookie_domain'));
}
public function test_setKey()
{
$this->assertNull(self::getPrivateField($this->chat, 'key'));
$this->chat->setKey('123456');
$this->assertEquals('123456', self::getPrivateField($this->chat, 'key'));
}
public function test_setCharset()
{
$this->assertEquals('utf-8', self::getPrivateField($this->chat, 'charset'));
$this->chat->setCharset('utf-32');
$this->assertEquals('utf-32', self::getPrivateField($this->chat, 'charset'));
}
public function test_setLanguage()
{
$this->assertEquals('en', self::getPrivateField($this->chat, 'language'));
$this->chat->setLanguage('cs');
$this->assertEquals('cs', self::getPrivateField($this->chat, 'language'));
}
/**
* @expectedException \Exception
* @expectedExceptionMessage At least KEY param must be set!
*/
public function test_render_keyNotSet()
{
$this->chat->render();
}
public function test_render_simple()
{
$this->chat->setKey('XYZ123456');
$ret = $this->chat->render();
$expected = "<script type=\"text/javascript\">
var _smartsupp = _smartsupp || {};
_smartsupp.key = 'XYZ123456';
window.smartsupp||(function(d) {
var s,c,o=smartsupp=function(){ o._.push(arguments)};o._=[];
s=d.getElementsByTagName('script')[0];c=d.createElement('script');
c.type='text/javascript';c.charset='utf-8';c.async=true;
c.src='//www.smartsuppchat.com/loader.js';s.parentNode.insertBefore(c,s);
})(document);
</script>";
$this->assertEquals($expected, $ret);
}
public function test_render_simpleOutput()
{
$this->chat->setKey('XYZ123456');
$ret = $this->chat->render(true);
$this->assertNull($ret);
$this->expectOutputRegex('/.*window.smartsupp.*/');
}
public function test_render_allParams()
{
$this->chat->setKey('XYZ123456');
$this->chat->setCookieDomain('.foo.bar');
$this->chat->disableSendEmailTranscript();
$this->chat->enableRating('advanced', true);
$this->chat->setAlign('left', 'side', 20, 120);
$this->chat->setWidget('button');
$this->chat->setName('Johny Depp');
$this->chat->setEmail('johny@depp.com');
$this->chat->setVariable('orderTotal', 'Total orders', 150);
$this->chat->setVariable('lastOrder', 'Last ordered', '2015-07-09');
$this->chat->setGoogleAnalytics('UA-123456', array('cookieDomain' => '.foo.bar'));
$this->chat->hideWidget();
$ret = $this->chat->render();
$this->assertEquals(file_get_contents(dirname(__FILE__) . '/chat_code.txt'), $ret);
}
/**
* Get private / protected field value using \ReflectionProperty object.
*
* @static
* @param mixed $object object to be used
* @param string $fieldName object property name
* @return mixed given property value
*/
public static function getPrivateField($object, $fieldName)
{
$refId = new \ReflectionProperty($object, $fieldName);
$refId->setAccessible(true);
$value = $refId->getValue($object);
$refId->setAccessible(false);
return $value;
}
}

View File

@@ -0,0 +1,6 @@
<?php
error_reporting(E_ALL | E_STRICT);
require __DIR__ . '/../vendor/autoload.php';
// set multibyte encoding to utf-8 to be sure. Some php configs have not utf-8 by default
mb_internal_encoding('UTF-8');

View File

@@ -0,0 +1,25 @@
<script type="text/javascript">
var _smartsupp = _smartsupp || {};
_smartsupp.key = 'XYZ123456';
_smartsupp.cookieDomain = '.foo.bar';
_smartsupp.sendEmailTanscript = false;
_smartsupp.ratingEnabled = true; // by default false
_smartsupp.ratingType = 'advanced'; // by default 'simple'
_smartsupp.ratingComment = true; // default false
_smartsupp.alignX = 'left'; // or 'left'
_smartsupp.alignY = 'side'; // by default 'bottom'
_smartsupp.widget = 'button'; // by default 'widget'
_smartsupp.offsetX = 20; // offset from left / right, default 10
_smartsupp.offsetY = 120; // offset from top, default 100
_smartsupp.gaKey = 'UA-123456';
_smartsupp.gaOptions = {'cookieDomain': '.foo.bar'};
_smartsupp.hideWidget = true;
window.smartsupp||(function(d) {
var s,c,o=smartsupp=function(){ o._.push(arguments)};o._=[];
s=d.getElementsByTagName('script')[0];c=d.createElement('script');
c.type='text/javascript';c.charset='utf-8';c.async=true;
c.src='//www.smartsuppchat.com/loader.js';s.parentNode.insertBefore(c,s);
})(document);smartsupp('email', 'johny@depp.com');
smartsupp('name', 'Johny Depp');
smartsupp('variables', {orderTotal: {label: 'Total orders', value: '150'}, lastOrder: {label: 'Last ordered', value: '2015-07-09'}});
</script>

View File

@@ -0,0 +1,5 @@
.idea/*
/vendor/
composer.lock
/build/
test.php

View File

@@ -0,0 +1,27 @@
language: php
php:
- 5.6
- 7.0
- 7.1
- 7.2
- 7.3
matrix:
include:
- php: 5.3
dist: precise
- php: 5.4
dist: trusty
- php: 5.5
dist: trusty
before_script:
- composer require satooshi/php-coveralls:1.0.1 squizlabs/php_codesniffer
- composer install
script:
- vendor/bin/phpcs src/ -p --standard=PSR2 --report=summary
- mkdir -p build/logs
- vendor/bin/phpunit --configuration phpunit.xml --coverage-text
after_success:
- sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php vendor/bin/coveralls -v; fi;'

View File

@@ -0,0 +1,92 @@
[![Build Status](https://travis-ci.org/smartsupp/php-auth-client.svg)](https://travis-ci.org/smartsupp/php-auth-client)
[![Coverage Status](https://coveralls.io/repos/github/smartsupp/php-auth-client/badge.svg?branch=master)](https://coveralls.io/github/smartsupp/php-auth-client?branch=master)
# Smartsupp Authentication API PHP client
* https://www.smartsupp.com/
## Get started
- Response is successfull if not contains `error` property in `$response` array.
- The `error` is machine-readable name of error, and `message` is human-readable description of error.
## create
```php
$api = new Smartsupp\Auth\Api();
$response = $api->create(array(
'email' => 'LOGIN_EMAIL', // required
'password' => 'YOUR_PASSWORD', // optional, min length 6 characters
'name' => 'John Doe', // optional
'lang' => 'en', // optional, lowercase; 2 characters
'partnerKey' => 'PARTNER_API_KEY' // optional
));
// print_r($response); // success response
array(
'account' => array(
'key' => 'CHAT_KEY',
'lang' => 'en'
),
'user' => array(
'email' => 'LOGIN_EMAIL',
'name' => 'John Doe',
'password' => 'YOUR_PASSWORD'
)
);
// print_r($response); // failure response
array(
'error' => 'EmailExists',
'message' => 'Email already exists',
'hint' => 'email'
);
```
### Errors
- `AuthError` - invalid PARTNER_KEY.
- `InvalidParam` - missing or invalid parameter (e.g.: email).
- `EmailExists` - email is already taken.
## login
```php
$api = new Smartsupp\Auth\Api();
$response = $api->login(array(
'email' => 'LOGIN_EMAIL',
'password' => 'YOUR_PASSWORD'
));
// print_r($response); // success response
array(
'account' => array(
'key' => 'CHAT_KEY',
'lang' => 'en'
)
);
// print_r($response); // failure response
array(
'error' => 'InvalidCredential',
'message' => 'Invalid password'
);
```
### Errors
- `AuthError` - invalid PARTNER_KEY.
- `InvalidParam` - missing or invalid parameter (e.g.: email is not valid, password is too short).
- `IdentityNotFound` - account with this email not exists.
- `InvalidCredential` - email exists, bad password is incorrect.
- `LoginFailure` - something is bad with login.
## Requirements
For backward compatibility with multiple plugins library supports PHP starting from version 5.3. It is highly possibly the constraint will change to 5.6+ in near future.
## Copyright
Copyright (c) 2016 Smartsupp.com, s.r.o.

View File

@@ -0,0 +1,29 @@
{
"name": "smartsupp/php-partner-client",
"description": "API client allows to register and login (obtain API key) from Smartsupp partner API.",
"type": "library",
"keywords": [
"chat"
],
"homepage": "https://www.smartsupp.com/",
"authors": [
{
"name": "Marek Gach",
"role": "lead"
}
],
"support": {
"issues": "https://github.com/smartsupp/php-partner-client/issues",
"wiki": "https://github.com/smartsupp/php-partner-client/wiki",
"source": "https://github.com/smartsupp/php-partner-client"
},
"autoload": {
"psr-0": {"Smartsupp": "src/"}
},
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"phpunit/phpunit": "4.8.*"
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="Smartsupp partner API client">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
</phpunit>

View File

@@ -0,0 +1,169 @@
<?php
namespace Smartsupp\Auth;
use Exception;
use Smartsupp\Auth\Request\CurlRequest;
use Smartsupp\Auth\Request\HttpRequest;
/**
* Class to communicate with Smartsupp partner API.
*
* PHP version >=5.3
*
* @package Smartsupp
* @author Marek Gach <gach@kurzor.net>
* @copyright since 2016 SmartSupp.com
* @version Git: $Id$
* @link https://github.com/smartsupp/php-partner-client
*/
class Api
{
/** API call base URL */
const API_BASE_URL = 'https://www.smartsupp.com/';
/** URL paths for all used resources endpoints methods */
const URL_LOGIN = 'account/login',
URL_CREATE = 'account/create';
/**
* @var null|CurlRequest
*/
private $handle = null;
/**
* Api constructor.
*
* @param null|HttpRequest $handle inject custom request handle to better unit test
* @throws Exception
*/
public function __construct(HttpRequest $handle = null)
{
// @codeCoverageIgnoreStart
if (!function_exists('curl_init')) {
throw new Exception('Smartsupp API client needs the CURL PHP extension.');
}
if (!function_exists('json_decode')) {
throw new Exception('Smartsupp API client needs the JSON PHP extension.');
}
// @codeCoverageIgnoreEnd
$this->handle = $handle ?: new CurlRequest();
}
/**
* Allows to create user.
*
* @param array $data
* @return array
*/
public function create($data)
{
return $this->post(self::URL_CREATE, $data);
}
/**
* Allows to log in account and obtain user key.
*
* @param array $data
* @return array
*/
public function login($data)
{
return $this->post(self::URL_LOGIN, $data);
}
/**
* Helper function to execute POST request.
*
* @param string $path request path
* @param array $data optional POST data array
* @return array|string array data or json encoded string of result
* @throws Exception
*/
private function post($path, $data)
{
return $this->run($path, 'post', $data);
}
/**
* Execute request against URL path with given method, optional data array. Also allows
* to specify if json data will be decoded before function return.
*
* @param $path request path
* @param $method request method
* @param null|array $data optional request data
* @param bool $json_decode specify if returned json data will be decoded
* @return string|array JSON data or array containing decoded JSON data
* @throws Exception
*/
private function run($path, $method, $data = null, $json_decode = true)
{
$this->handle->setOption(CURLOPT_URL, self::API_BASE_URL . $path);
$this->handle->setOption(CURLOPT_RETURNTRANSFER, true);
$this->handle->setOption(CURLOPT_FAILONERROR, false);
$this->handle->setOption(CURLOPT_SSL_VERIFYPEER, true);
$this->handle->setOption(CURLOPT_SSL_VERIFYHOST, 2);
$this->handle->setOption(CURLOPT_USERAGENT, 'cURL:php-partner-client');
// forward headers from request
$headers = array(
'X-Forwarded-For: ' . $this->getUserIpAddr(),
'Accept-Language: ' . $this->getAcceptLanguage(),
);
$this->handle->setOption(CURLOPT_HTTPHEADER, $headers);
switch ($method) {
case 'post':
$this->handle->setOption(CURLOPT_POST, true);
$this->handle->setOption(CURLOPT_POSTFIELDS, $data);
break;
}
$response = $this->handle->execute();
if ($response === false) {
throw new Exception($this->handle->getLastErrorMessage());
}
$this->handle->close();
if ($json_decode) {
$response = json_decode($response, true);
if (json_last_error() != JSON_ERROR_NONE) {
throw new Exception('Cannot parse API response JSON. Error: ' . json_last_error_msg());
}
}
return $response;
}
/**
* Return user IP address.
*
* @return string|null
*/
private function getUserIpAddr()
{
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
// ip from share internet
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
// ip pass from proxy
return $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
// in case is not set - may be in CLI
return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
}
}
/**
* Get Accept-Language header.
*
* @return string|null
*/
private function getAcceptLanguage()
{
return isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : null;
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Smartsupp\Auth\Request;
/**
* Class CurlRequest implements basic functionality to handle cURL requests.
* It is used to better mock this communication in PHPUnit tests.
*
* @package Smartsupp\Request
*/
class CurlRequest implements HttpRequest
{
/**
* Curl handler resource.
*
* @var null|resource
*/
private $handle = null;
/**
* CurlRequest constructor.
*
* @param string|null $url URL address to make call for
*/
public function __construct($url = null)
{
$this->init($url);
}
/**
* Init cURL connection object.
*
* @param string|null $url
* @throws Exception
*/
public function init($url = null)
{
$this->handle = curl_init($url);
}
/**
* Set cURL option with given value.
*
* @param string $name option name
* @param string|array $value option value
*/
public function setOption($name, $value)
{
curl_setopt($this->handle, $name, $value);
}
/**
* Execute cURL request.
*
* @return boolean
*/
public function execute()
{
return curl_exec($this->handle);
}
/**
* Get array of information about last request.
*
* @param int $opt options
* @return array info array
*/
public function getInfo($opt = 0)
{
return curl_getinfo($this->handle, $opt);
}
/**
* Close cURL handler connection.
*/
public function close()
{
curl_close($this->handle);
}
/**
* Return last error message as string.
*
* @return string formatted error message
*/
public function getLastErrorMessage()
{
$message = sprintf("cURL failed with error #%d: %s", curl_errno($this->handle), curl_error($this->handle));
return $message;
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Smartsupp\Auth\Request;
/**
* Interface HttpRequest serves as base interface for concrete Request implementations.
*
* @package Smartsupp\Request
*/
interface HttpRequest
{
/**
* Allows to set request options.
*
* @param string $name option name
* @param string|array $value option value
*/
public function setOption($name, $value);
/**
* Execute request call.
*
* @return boolean execution status
*/
public function execute();
/**
* Get request status info.
*
* @param int $opt options
* @return array status info array
*/
public function getInfo($opt = 0);
/**
* Close request connection.
*
* @return boolean close status
*/
public function close();
/**
* Get last error message as formated string.
*
* @return string formated error message
*/
public function getLastErrorMessage();
}

View File

@@ -0,0 +1,94 @@
<?php
namespace Smartsupp\Auth;
use Exception;
class ApiTest extends \PHPUnit_Framework_TestCase
{
public function test_constructor()
{
$api = new Api();
$this->assertNotNull($api);
}
public function test_login()
{
$data = array(
'email' => 'test5@kurzor.net',
'password' => 'xxx'
);
$response = array(
'account' => array(
'key' => 'CHAT_KEY',
'lang' => 'en'
)
);
$http = $this->getMock('Smartsupp\Auth\Request\HttpRequest');
$http->expects($this->any())
->method('execute')
->will($this->returnValue(json_encode($response)));
// create class under test using $http instead of a real CurlRequest
$api = new Api($http);
$this->assertEquals($response, $api->login($data));
}
/**
* @expectedException Exception
* @expectedExceptionMessage Foo is at bar!
*/
public function test_response_error()
{
$data = array(
'email' => 'test5@kurzor.net',
'password' => 'xxx'
);
$http = $this->getMock('Smartsupp\Auth\Request\HttpRequest');
$http->expects($this->any())
->method('execute')
->will($this->returnValue(false));
$http->expects($this->any())
->method('getLastErrorMessage')
->will($this->returnValue('Foo is at bar!'));
// create class under test using $http instead of a real CurlRequest
$api = new Api($http);
$api->login($data);
}
public function test_create()
{
$data = array(
'email' => 'LOGIN_EMAIL', // required
'password' => 'YOUR_PASSWORD', // optional, min length 6 characters
'name' => 'John Doe', // optional
'lang' => 'en', // optional, lowercase; 2 characters
'partnerKey' => 'PARTNER_API_KEY' // optional
);
$response = array(
'account' => array(
'key' => 'CHAT_KEY',
'lang' => 'en'
),
'user' => array(
'email' => 'LOGIN_EMAIL',
'name' => 'John Doe',
'password' => 'YOUR_PASSWORD'
)
);
$http = $this->getMock('Smartsupp\Auth\Request\HttpRequest');
$http->expects($this->any())
->method('execute')
->will($this->returnValue(json_encode($response)));
// create class under test using $http instead of a real CurlRequest
$api = new Api($http);
$this->assertEquals($response, $api->create($data));
}
}

View File

@@ -0,0 +1,75 @@
<?php
namespace Smartsupp\Auth\Request;
class CurlRequestTest extends \PHPUnit_Framework_TestCase
{
/**
* @var CurlRequest
*/
protected $curl;
protected function setUp()
{
parent::setUp();
$this->curl = new CurlRequest('https://www.smartsupp.com/cs/product');
}
public function test_constructorVoid()
{
$this->curl = new CurlRequest();
$this->assertInstanceOf('Smartsupp\Auth\Request\CurlRequest', $this->curl);
}
public function test_constructorUrl()
{
$this->curl = new CurlRequest('https://smartsupp.com');
$this->assertInstanceOf('Smartsupp\Auth\Request\CurlRequest', $this->curl);
}
public function test_initError()
{
$this->curl->init('https://smartsupp.com');
}
public function test_setOption()
{
$this->curl->setOption(CURLOPT_HEADER, 0);
}
/**
* @expectedExceptionMessage curl_setopt() expects parameter 1 to be resource, null given
*/
public function test_setOption_notInitialized()
{
$this->curl->setOption(CURLOPT_HEADER, 0);
}
public function test_close()
{
$this->curl->close();
}
public function test_execute()
{
$this->curl->setOption(CURLOPT_RETURNTRANSFER, TRUE);
$this->assertNotEmpty($this->curl->execute());
}
public function test_getInfo()
{
$this->curl->setOption(CURLOPT_RETURNTRANSFER, TRUE);
$this->curl->execute();
$this->assertEquals($this->curl->getInfo(CURLINFO_HTTP_CODE), 200);
}
public function test_getLastErrorMessage()
{
$this->curl->setOption(CURLOPT_URL, 'foo://bar');
$this->curl->setOption(CURLOPT_RETURNTRANSFER, TRUE);
$this->curl->execute();
$this->assertNotEmpty($this->curl->getLastErrorMessage());
}
}

View File

@@ -0,0 +1,6 @@
<?php
error_reporting(E_ALL | E_STRICT);
require __DIR__ . '/../vendor/autoload.php';
// set multibyte encoding to utf-8 to be sure. Some php configs have not utf-8 by default
mb_internal_encoding('UTF-8');