mirror of
https://github.com/shlinkio/shlink.git
synced 2024-11-23 21:27:44 +03:00
Merge pull request #1319 from acelaya-forks/feature/emoji-support
Feature/emoji support
This commit is contained in:
commit
8cfb14198b
45 changed files with 231 additions and 89 deletions
|
@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
|
|||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
* [#767](https://github.com/shlinkio/shlink/issues/767) Added full support to use emojis everywhere, whether it is custom slugs, titles, referrers, etc.
|
||||
* [#1274](https://github.com/shlinkio/shlink/issues/1274) Added support to filter short URLs lists by all provided tags.
|
||||
|
||||
The `GET /short-urls` endpoint now accepts a `tagsMode=all` param which will make only short URLs matching **all** the tags in the `tags[]` query param, to be returned.
|
||||
|
|
|
@ -21,6 +21,13 @@ return (static function (): array {
|
|||
'mssql' => '1433',
|
||||
default => '3306',
|
||||
};
|
||||
$resolveCharset = static fn () => match ($driver) {
|
||||
// This does not determine charsets or collations in tables or columns, but the charset used in the data
|
||||
// flowing in the connection, so it has to match what has been set in the database.
|
||||
'maria', 'mysql' => 'utf8mb4',
|
||||
'postgres' => 'utf8',
|
||||
default => null,
|
||||
};
|
||||
$resolveConnection = static fn () => match ($driver) {
|
||||
null, 'sqlite' => [
|
||||
'driver' => 'pdo_sqlite',
|
||||
|
@ -34,7 +41,7 @@ return (static function (): array {
|
|||
'host' => env('DB_HOST', $driver === 'postgres' ? env('DB_UNIX_SOCKET') : null),
|
||||
'port' => env('DB_PORT', $resolveDefaultPort()),
|
||||
'unix_socket' => $isMysqlCompatible ? env('DB_UNIX_SOCKET') : null,
|
||||
'charset' => 'utf8',
|
||||
'charset' => $resolveCharset(),
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ return [
|
|||
'driver' => 'pdo_mysql',
|
||||
'host' => 'shlink_db_mysql',
|
||||
'dbname' => 'shlink',
|
||||
'charset' => 'utf8mb4',
|
||||
],
|
||||
],
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ $buildDbConnection = static function (): array {
|
|||
'user' => 'postgres',
|
||||
'password' => 'root',
|
||||
'dbname' => 'shlink_test',
|
||||
'charset' => 'utf8',
|
||||
],
|
||||
'mssql' => [
|
||||
'driver' => 'pdo_sqlsrv',
|
||||
|
@ -70,6 +71,7 @@ $buildDbConnection = static function (): array {
|
|||
'user' => 'root',
|
||||
'password' => 'root',
|
||||
'dbname' => 'shlink_test',
|
||||
'charset' => 'utf8mb4',
|
||||
],
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,45 +5,45 @@ declare(strict_types=1);
|
|||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
use function is_subclass_of;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
class Version20160819142757 extends AbstractMigration
|
||||
{
|
||||
private const MYSQL = 'mysql';
|
||||
private const SQLITE = 'sqlite';
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$db = $this->connection->getDatabasePlatform()->getName();
|
||||
$platformClass = $this->connection->getDatabasePlatform();
|
||||
$table = $schema->getTable('short_urls');
|
||||
$column = $table->getColumn('short_code');
|
||||
|
||||
if ($db === self::MYSQL) {
|
||||
$column->setPlatformOption('collation', 'utf8_bin');
|
||||
} elseif ($db === self::SQLITE) {
|
||||
$column->setPlatformOption('collate', 'BINARY');
|
||||
}
|
||||
match (true) {
|
||||
is_subclass_of($platformClass, MySQLPlatform::class) => $column
|
||||
->setPlatformOption('charset', 'utf8mb4')
|
||||
->setPlatformOption('collation', 'utf8mb4_bin'),
|
||||
is_subclass_of($platformClass, SqlitePlatform::class) => $column->setPlatformOption('collate', 'BINARY'),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->connection->getDatabasePlatform()->getName();
|
||||
// Nothing to roll back
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -76,6 +77,6 @@ class Version20160820191203 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
@ -48,6 +49,6 @@ class Version20171021093246 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
@ -45,6 +46,6 @@ class Version20171022064541 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -42,6 +43,6 @@ final class Version20180801183328 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use PDO;
|
||||
|
@ -69,6 +70,6 @@ final class Version20180913205455 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -50,6 +51,6 @@ final class Version20180915110857 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
|
@ -58,7 +59,7 @@ final class Version20181020060559 extends AbstractMigration
|
|||
foreach (self::COLUMNS as $camelCaseName => $snakeCaseName) {
|
||||
$qb->set($snakeCaseName, $camelCaseName);
|
||||
}
|
||||
$qb->execute();
|
||||
$qb->executeStatement();
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
|
@ -68,6 +69,6 @@ final class Version20181020060559 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -41,6 +42,6 @@ final class Version20181020065148 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
@ -37,6 +38,6 @@ final class Version20181110175521 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
@ -37,6 +38,6 @@ final class Version20190824075137 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
@ -55,6 +56,6 @@ final class Version20190930165521 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
@ -49,6 +50,6 @@ final class Version20191001201532 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
@ -37,6 +38,6 @@ final class Version20191020074522 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ declare(strict_types=1);
|
|||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -38,7 +40,7 @@ final class Version20200105165647 extends AbstractMigration
|
|||
'zeroValue' => '0',
|
||||
'emptyString' => '',
|
||||
])
|
||||
->execute();
|
||||
->executeStatement();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,14 +63,14 @@ final class Version20200105165647 extends AbstractMigration
|
|||
*/
|
||||
public function postUp(Schema $schema): void
|
||||
{
|
||||
$platformName = $this->connection->getDatabasePlatform()->getName();
|
||||
$castType = $platformName === 'postgres' ? 'DOUBLE PRECISION' : 'DECIMAL(9,2)';
|
||||
$isPostgres = $this->connection->getDatabasePlatform() instanceof PostgreSQLPlatform;
|
||||
$castType = $isPostgres ? 'DOUBLE PRECISION' : 'DECIMAL(9,2)';
|
||||
|
||||
foreach (self::COLUMNS as $newName => $oldName) {
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('visit_locations')
|
||||
->set($newName, 'CAST(' . $oldName . ' AS ' . $castType . ')')
|
||||
->execute();
|
||||
->executeStatement();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +80,7 @@ final class Version20200105165647 extends AbstractMigration
|
|||
$qb = $this->connection->createQueryBuilder();
|
||||
$qb->update('visit_locations')
|
||||
->set($oldName, $newName)
|
||||
->execute();
|
||||
->executeStatement();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,6 +98,6 @@ final class Version20200105165647 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -47,6 +48,6 @@ final class Version20200106215144 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
|
@ -36,6 +38,9 @@ final class Version20200110182849 extends AbstractMigration
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function setDefaultValueForColumnInTable(string $tableName, string $columnName): void
|
||||
{
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
|
@ -43,7 +48,7 @@ final class Version20200110182849 extends AbstractMigration
|
|||
->set($columnName, ':emptyValue')
|
||||
->setParameter('emptyValue', self::DEFAULT_EMPTY_VALUE)
|
||||
->where($qb->expr()->isNull($columnName))
|
||||
->execute();
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
|
@ -53,6 +58,6 @@ final class Version20200110182849 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -32,7 +33,7 @@ final class Version20200323190014 extends AbstractMigration
|
|||
->andWhere($qb->expr()->eq('lon', 0))
|
||||
->setParameter('isEmpty', true)
|
||||
->setParameter('emptyString', '')
|
||||
->execute();
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
|
@ -45,6 +46,6 @@ final class Version20200323190014 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
|
@ -27,6 +28,6 @@ final class Version20200503170404 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -44,6 +45,6 @@ final class Version20201023090929 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace ShlinkMigrations;
|
|||
|
||||
use Cake\Chronos\Chronos;
|
||||
use Doctrine\DBAL\Driver\Result;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -86,6 +87,6 @@ final class Version20201102113208 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -52,6 +53,6 @@ final class Version20210102174433 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
|
@ -26,6 +27,6 @@ final class Version20210118153932 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -36,6 +37,6 @@ final class Version20210202181026 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -43,6 +44,6 @@ final class Version20210207100807 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -37,6 +38,6 @@ final class Version20210306165711 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -26,6 +27,6 @@ final class Version20210522051601 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -28,6 +29,6 @@ final class Version20210522124633 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
@ -41,6 +42,6 @@ final class Version20210720143824 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
@ -26,6 +27,6 @@ final class Version20211002072605 extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
73
data/migrations/Version20220110113313.php
Normal file
73
data/migrations/Version20220110113313.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ShlinkMigrations;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20220110113313 extends AbstractMigration
|
||||
{
|
||||
private const CHARSET = 'utf8mb4';
|
||||
private const COLLATIONS = [
|
||||
'short_urls' => [
|
||||
'original_url' => 'unicode_ci',
|
||||
'short_code' => 'bin',
|
||||
'import_original_short_code' => 'unicode_ci',
|
||||
'title' => 'unicode_ci',
|
||||
],
|
||||
'domains' => [
|
||||
'authority' => 'unicode_ci',
|
||||
'base_url_redirect' => 'unicode_ci',
|
||||
'regular_not_found_redirect' => 'unicode_ci',
|
||||
'invalid_short_url_redirect' => 'unicode_ci',
|
||||
],
|
||||
'tags' => [
|
||||
'name' => 'unicode_ci',
|
||||
],
|
||||
'visits' => [
|
||||
'referer' => 'unicode_ci',
|
||||
'user_agent' => 'unicode_ci',
|
||||
'visited_url' => 'unicode_ci',
|
||||
],
|
||||
'visit_locations' => [
|
||||
'country_code' => 'unicode_ci',
|
||||
'country_name' => 'unicode_ci',
|
||||
'region_name' => 'unicode_ci',
|
||||
'city_name' => 'unicode_ci',
|
||||
'timezone' => 'unicode_ci',
|
||||
],
|
||||
];
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->skipIf(! $this->isMySql(), 'This only sets MySQL-specific database options');
|
||||
|
||||
foreach (self::COLLATIONS as $tableName => $columns) {
|
||||
$table = $schema->getTable($tableName);
|
||||
|
||||
foreach ($columns as $columnName => $collation) {
|
||||
$table->getColumn($columnName)
|
||||
->setPlatformOption('charset', self::CHARSET)
|
||||
->setPlatformOption('collation', self::CHARSET . '_' . $collation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// No down
|
||||
}
|
||||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return ! $this->isMySql();
|
||||
}
|
||||
|
||||
private function isMySql(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform() instanceof MySQLPlatform;
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace <namespace>;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
|
@ -21,6 +22,6 @@ final class <className> extends AbstractMigration
|
|||
|
||||
public function isTransactional(): bool
|
||||
{
|
||||
return $this->connection->getDatabasePlatform()->getName() !== 'mysql';
|
||||
return ! ($this->connection->getDatabasePlatform() instanceof MySQLPlatform);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,21 +21,21 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
|||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
$builder->createField('authority', Types::STRING)
|
||||
fieldWithUtf8Charset($builder->createField('authority', Types::STRING), $emConfig)
|
||||
->unique()
|
||||
->build();
|
||||
|
||||
$builder->createField('baseUrlRedirect', Types::STRING)
|
||||
fieldWithUtf8Charset($builder->createField('baseUrlRedirect', Types::STRING), $emConfig)
|
||||
->columnName('base_url_redirect')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('regular404Redirect', Types::STRING)
|
||||
fieldWithUtf8Charset($builder->createField('regular404Redirect', Types::STRING), $emConfig)
|
||||
->columnName('regular_not_found_redirect')
|
||||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('invalidShortUrlRedirect', Types::STRING)
|
||||
fieldWithUtf8Charset($builder->createField('invalidShortUrlRedirect', Types::STRING), $emConfig)
|
||||
->columnName('invalid_short_url_redirect')
|
||||
->nullable()
|
||||
->build();
|
||||
|
|
|
@ -23,12 +23,12 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
|||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
$builder->createField('longUrl', Types::STRING)
|
||||
fieldWithUtf8Charset($builder->createField('longUrl', Types::STRING), $emConfig)
|
||||
->columnName('original_url')
|
||||
->length(2048)
|
||||
->build();
|
||||
|
||||
$builder->createField('shortCode', Types::STRING)
|
||||
fieldWithUtf8Charset($builder->createField('shortCode', Types::STRING), $emConfig, 'bin')
|
||||
->columnName('short_code')
|
||||
->length(255)
|
||||
->build();
|
||||
|
@ -57,7 +57,7 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
|||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('importOriginalShortCode', Types::STRING)
|
||||
fieldWithUtf8Charset($builder->createField('importOriginalShortCode', Types::STRING), $emConfig)
|
||||
->columnName('import_original_short_code')
|
||||
->nullable()
|
||||
->build();
|
||||
|
@ -85,7 +85,7 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
|||
|
||||
$builder->addUniqueConstraint(['short_code', 'domain_id'], 'unique_short_code_plus_domain');
|
||||
|
||||
$builder->createField('title', Types::STRING)
|
||||
fieldWithUtf8Charset($builder->createField('title', Types::STRING), $emConfig)
|
||||
->columnName('title')
|
||||
->length(512)
|
||||
->nullable()
|
||||
|
|
|
@ -21,7 +21,7 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
|||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
$builder->createField('name', Types::STRING)
|
||||
fieldWithUtf8Charset($builder->createField('name', Types::STRING), $emConfig)
|
||||
->unique()
|
||||
->build();
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
|||
->option('unsigned', true)
|
||||
->build();
|
||||
|
||||
$builder->createField('referer', Types::STRING)
|
||||
fieldWithUtf8Charset($builder->createField('referer', Types::STRING), $emConfig)
|
||||
->nullable()
|
||||
->length(Visitor::REFERER_MAX_LENGTH)
|
||||
->build();
|
||||
|
@ -40,7 +40,7 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
|||
->nullable()
|
||||
->build();
|
||||
|
||||
$builder->createField('userAgent', Types::STRING)
|
||||
fieldWithUtf8Charset($builder->createField('userAgent', Types::STRING), $emConfig)
|
||||
->columnName('user_agent')
|
||||
->length(Visitor::USER_AGENT_MAX_LENGTH)
|
||||
->nullable()
|
||||
|
@ -55,7 +55,7 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
|||
->cascadePersist()
|
||||
->build();
|
||||
|
||||
$builder->createField('visitedUrl', Types::STRING)
|
||||
fieldWithUtf8Charset($builder->createField('visitedUrl', Types::STRING), $emConfig)
|
||||
->columnName('visited_url')
|
||||
->length(Visitor::VISITED_URL_MAX_LENGTH)
|
||||
->nullable()
|
||||
|
|
|
@ -29,7 +29,7 @@ return static function (ClassMetadata $metadata, array $emConfig): void {
|
|||
];
|
||||
|
||||
foreach ($columns as $columnName => $fieldName) {
|
||||
$builder->createField($fieldName, Types::STRING)
|
||||
fieldWithUtf8Charset($builder->createField($fieldName, Types::STRING), $emConfig)
|
||||
->columnName($columnName)
|
||||
->nullable()
|
||||
->build();
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\Core;
|
|||
|
||||
use Cake\Chronos\Chronos;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\ORM\Mapping\Builder\FieldBuilder;
|
||||
use Jaybizzle\CrawlerDetect\CrawlerDetect;
|
||||
use Laminas\InputFilter\InputFilter;
|
||||
use PUGX\Shortid\Factory as ShortIdFactory;
|
||||
|
@ -13,13 +14,10 @@ use Shlinkio\Shlink\Common\Util\DateRange;
|
|||
|
||||
use function Functional\reduce_left;
|
||||
use function is_array;
|
||||
use function lcfirst;
|
||||
use function print_r;
|
||||
use function Shlinkio\Shlink\Common\buildDateRange;
|
||||
use function sprintf;
|
||||
use function str_repeat;
|
||||
use function str_replace;
|
||||
use function ucwords;
|
||||
|
||||
function generateRandomShortCode(int $length): string
|
||||
{
|
||||
|
@ -34,7 +32,7 @@ function generateRandomShortCode(int $length): string
|
|||
|
||||
function parseDateFromQuery(array $query, string $dateName): ?Chronos
|
||||
{
|
||||
return ! isset($query[$dateName]) || empty($query[$dateName]) ? null : Chronos::parse($query[$dateName]);
|
||||
return empty($query[$dateName] ?? null) ? null : Chronos::parse($query[$dateName]);
|
||||
}
|
||||
|
||||
function parseDateRangeFromQuery(array $query, string $startDateName, string $endDateName): DateRange
|
||||
|
@ -100,11 +98,6 @@ function arrayToString(array $array, int $indentSize = 4): string
|
|||
}, '');
|
||||
}
|
||||
|
||||
function kebabCaseToCamelCase(string $name): string
|
||||
{
|
||||
return lcfirst(str_replace(' ', '', ucwords(str_replace('-', ' ', $name))));
|
||||
}
|
||||
|
||||
function isCrawler(string $userAgent): bool
|
||||
{
|
||||
static $detector;
|
||||
|
@ -114,3 +107,12 @@ function isCrawler(string $userAgent): bool
|
|||
|
||||
return $detector->isCrawler($userAgent);
|
||||
}
|
||||
|
||||
function fieldWithUtf8Charset(FieldBuilder $field, array $emConfig, string $collation = 'unicode_ci'): FieldBuilder
|
||||
{
|
||||
return match ($emConfig['connection']['driver'] ?? null) {
|
||||
'pdo_mysql' => $field->option('charset', 'utf8mb4')
|
||||
->option('collation', 'utf8mb4_' . $collation),
|
||||
default => $field,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,19 +17,17 @@ class ShortUrlStringifier implements ShortUrlStringifierInterface
|
|||
|
||||
public function stringify(ShortUrl $shortUrl): string
|
||||
{
|
||||
return (new Uri())->withPath($shortUrl->getShortCode())
|
||||
->withScheme($this->domainConfig['schema'] ?? 'http')
|
||||
->withHost($this->resolveDomain($shortUrl))
|
||||
->__toString();
|
||||
$uriWithoutShortCode = (new Uri())->withScheme($this->domainConfig['schema'] ?? 'http')
|
||||
->withHost($this->resolveDomain($shortUrl))
|
||||
->withPath($this->basePath)
|
||||
->__toString();
|
||||
|
||||
// The short code needs to be appended to avoid it from being URL-encoded
|
||||
return sprintf('%s/%s', $uriWithoutShortCode, $shortUrl->getShortCode());
|
||||
}
|
||||
|
||||
private function resolveDomain(ShortUrl $shortUrl): string
|
||||
{
|
||||
$domain = $shortUrl->getDomain();
|
||||
if ($domain === null) {
|
||||
return $this->domainConfig['hostname'] ?? '';
|
||||
}
|
||||
|
||||
return sprintf('%s%s', $domain->getAuthority(), $this->basePath);
|
||||
return $shortUrl->getDomain()?->getAuthority() ?? $this->domainConfig['hostname'] ?? '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,18 @@ class ShortUrlStringifierTest extends TestCase
|
|||
$shortUrlWithShortCode('bar'),
|
||||
'http://example.com/bar',
|
||||
];
|
||||
yield 'special chars in short code' => [
|
||||
['hostname' => 'example.com'],
|
||||
'',
|
||||
$shortUrlWithShortCode('グーグル'),
|
||||
'http://example.com/グーグル',
|
||||
];
|
||||
yield 'emojis in short code' => [
|
||||
['hostname' => 'example.com'],
|
||||
'',
|
||||
$shortUrlWithShortCode('🦣-🍅'),
|
||||
'http://example.com/🦣-🍅',
|
||||
];
|
||||
yield 'hostname with base path in config' => [
|
||||
['hostname' => 'example.com/foo/bar'],
|
||||
'',
|
||||
|
|
|
@ -315,11 +315,22 @@ class CreateShortUrlTest extends ApiTestCase
|
|||
yield ['https://mobile.twitter.com/shlinkio/status/1360637738421268481'];
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function canCreateShortUrlsWithEmojis(): void
|
||||
{
|
||||
[$statusCode, $payload] = $this->createShortUrl([
|
||||
'longUrl' => 'https://emojipedia.org/fire/',
|
||||
'title' => '🔥🔥🔥',
|
||||
'customSlug' => '🦣🦣🦣',
|
||||
]);
|
||||
self::assertEquals(self::STATUS_OK, $statusCode);
|
||||
self::assertEquals('🔥🔥🔥', $payload['title']);
|
||||
self::assertEquals('🦣🦣🦣', $payload['shortCode']);
|
||||
self::assertEquals('http://doma.in/🦣🦣🦣', $payload['shortUrl']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array {
|
||||
* @var int $statusCode
|
||||
* @var array $payload
|
||||
* }
|
||||
* @return array{int $statusCode, array $payload}
|
||||
*/
|
||||
private function createShortUrl(array $body = [], string $apiKey = 'valid_api_key'): array
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue