diff --git a/.travis.yml b/.travis.yml index ec9a3b8..609eb90 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,10 @@ language: php -dist: precise +dist: trusty -sudo: false +cache: + directories: + - $HOME/.composer/cache/files php: - 5.6 @@ -10,25 +12,29 @@ php: - 7.1 env: - - DB=SQLITE CORE_RELEASE=4 PDO=1 + global: + - DB=SQLITE + - PDO=1 matrix: fast_finish: true include: - php: 5.6 - env: DB=SQLITE CORE_RELEASE=4 PDO=0 + env: PDO=0 PHPCS_TEST=1 before_script: # Init PHP - - printf "\n" | pecl install imagick - phpenv rehash - phpenv config-rm xdebug.ini + - export PATH=~/.composer/vendor/bin:$PATH - echo 'memory_limit = 2048M' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini # Install composer dependencies - composer validate - - composer require symfony/config:^3.2 silverstripe/framework:4.0.x-dev silverstripe/cms:4.0.x-dev silverstripe/siteconfig:4.0.x-dev silverstripe/config:1.0.x-dev silverstripe/admin:1.0.x-dev silverstripe/assets:1.0.x-dev silverstripe/versioned:1.0.x-dev --no-update + - composer require --no-update silverstripe/recipe-cms:1.0.x-dev - composer install --prefer-dist --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile + - if [[ $PHPCS_TEST ]]; then composer global require squizlabs/php_codesniffer:^3 --prefer-dist --no-interaction --no-progress --no-suggest -o; fi script: - vendor/bin/phpunit vendor/silverstripe/framework/tests + - if [[ $PHPCS_TEST ]]; then composer run-script lint; fi diff --git a/_configure_database.php b/_configure_database.php index a58391b..7948227 100644 --- a/_configure_database.php +++ b/_configure_database.php @@ -1,14 +1,25 @@ array( - 'title' => 'Directory path
Absolute path to directory, writeable by the webserver user.
' - . 'Recommended to be outside of your webroot
', - 'default' => dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . '.sqlitedb' - ), - 'database' => array( - 'title' => 'Database filename (extension .sqlite)', - 'default' => 'database.sqlite' - ) + 'path' => array( + 'title' => 'Directory path
Absolute path to directory, writeable by the webserver user.
' + . 'Recommended to be outside of your webroot
', + 'default' => dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . '.sqlitedb' + ), + 'database' => array( + 'title' => 'Database filename (extension .sqlite)', + 'default' => 'database.sqlite' + ) ); // Basic SQLLite3 Database /** @skipUpgrade */ DatabaseAdapterRegistry::register( - array( - 'class' => 'SQLite3Database', + array( + 'class' => 'SQLite3Database', 'module' => 'sqlite3', - 'title' => 'SQLite 3.3+ (using SQLite3)', - 'helperPath' => __DIR__.'/code/SQLiteDatabaseConfigurationHelper.php', + 'title' => 'SQLite 3.3+ (using SQLite3)', + 'helperPath' => __DIR__.'/code/SQLiteDatabaseConfigurationHelper.php', 'helperClass' => SQLiteDatabaseConfigurationHelper::class, - 'supported' => class_exists('SQLite3'), - 'missingExtensionText' => 'The SQLite3 + 'supported' => class_exists('SQLite3'), + 'missingExtensionText' => 'The SQLite3 PHP Extension is not available. Please install or enable it of them and refresh this page.', - 'fields' => array_merge($sqliteDatabaseAdapterRegistryFields, array('key' => array( - 'title' => 'Encryption key
This function is experimental and requires configuration of an ' - . 'encryption module', - 'default' => '' - ))) - ) + 'fields' => array_merge($sqliteDatabaseAdapterRegistryFields, array('key' => array( + 'title' => 'Encryption key
This function is experimental and requires configuration of an ' + . 'encryption module', + 'default' => '' + ))) + ) ); // PDO database /** @skipUpgrade */ DatabaseAdapterRegistry::register( - array( - 'class' => 'SQLite3PDODatabase', + array( + 'class' => 'SQLite3PDODatabase', 'module' => 'sqlite3', - 'title' => 'SQLite 3.3+ (using PDO)', - 'helperPath' => __DIR__.'/code/SQLiteDatabaseConfigurationHelper.php', + 'title' => 'SQLite 3.3+ (using PDO)', + 'helperPath' => __DIR__.'/code/SQLiteDatabaseConfigurationHelper.php', 'helperClass' => SQLiteDatabaseConfigurationHelper::class, - 'supported' => (class_exists('PDO') && in_array('sqlite', PDO::getAvailableDrivers())), - 'missingExtensionText' => - 'Either the PDO Extension or the + 'supported' => (class_exists('PDO') && in_array('sqlite', PDO::getAvailableDrivers())), + 'missingExtensionText' => + 'Either the PDO Extension or the SQLite3 PDO Driver are unavailable. Please install or enable these and refresh this page.', - 'fields' => $sqliteDatabaseAdapterRegistryFields - ) + 'fields' => $sqliteDatabaseAdapterRegistryFields + ) ); diff --git a/code/SQLite3Connector.php b/code/SQLite3Connector.php index 4a8c79d..871294c 100644 --- a/code/SQLite3Connector.php +++ b/code/SQLite3Connector.php @@ -111,7 +111,7 @@ class SQLite3Connector extends DBConnector case 'array': case 'unknown type': default: - user_error("Cannot bind parameter \"$value\" as it is an unsupported type ($phpType)", E_USER_ERROR); + $this->databaseError("Cannot bind parameter \"$value\" as it is an unsupported type ($phpType)"); break; } $values[] = array( @@ -176,7 +176,7 @@ class SQLite3Connector extends DBConnector public function selectDatabase($name) { if ($name !== $this->databaseName) { - user_error("SQLite3Connector can't change databases. Please create a new database connection", E_USER_ERROR); + $this->databaseError("SQLite3Connector can't change databases. Please create a new database connection"); } return true; } diff --git a/code/SQLite3Database.php b/code/SQLite3Database.php index e617cad..0252b35 100644 --- a/code/SQLite3Database.php +++ b/code/SQLite3Database.php @@ -20,6 +20,16 @@ class SQLite3Database extends Database { use Configurable; + /** + * Global environment config for setting 'path' + */ + const ENV_PATH = 'SS_SQLITE_DATABASE_PATH'; + + /** + * Global environment config for setting 'key' + */ + const ENV_KEY = 'SS_SQLITE_DATABASE_KEY'; + /** * Extension added to every database name * @@ -178,7 +188,8 @@ class SQLite3Database extends Database * * @return string|null */ - public function getPath() { + public function getPath() + { if ($this->getLivesInMemory()) { return null; } @@ -278,14 +289,22 @@ class SQLite3Database extends Database * @param bool $invertedMatch * @return PaginatedList DataObjectSet of result pages */ - public function searchEngine($classesToSearch, $keywords, $start, $pageLength, $sortBy = "Relevance DESC", - $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false + public function searchEngine( + $classesToSearch, + $keywords, + $start, + $pageLength, + $sortBy = "Relevance DESC", + $extraFilter = "", + $booleanSearch = false, + $alternativeFileFilter = "", + $invertedMatch = false ) { $keywords = $this->escapeString(str_replace(array('*', '+', '-', '"', '\''), '', $keywords)); $htmlEntityKeywords = htmlentities(utf8_decode($keywords)); $pageClass = 'SilverStripe\\CMS\\Model\\SiteTree'; - $fileClass = 'SilverStripe\\Assets\\File'; + $fileClass = 'SilverStripe\\Assets\\File'; $extraFilters = array($pageClass => '', $fileClass => ''); @@ -311,17 +330,24 @@ class SQLite3Database extends Database $notMatch = $invertedMatch ? "NOT " : ""; if ($keywords) { - $match[$pageClass] = " - (Title LIKE '%$keywords%' OR MenuTitle LIKE '%$keywords%' OR Content LIKE '%$keywords%' OR MetaDescription LIKE '%$keywords%' OR - Title LIKE '%$htmlEntityKeywords%' OR MenuTitle LIKE '%$htmlEntityKeywords%' OR Content LIKE '%$htmlEntityKeywords%' OR MetaDescription LIKE '%$htmlEntityKeywords%') - "; + $match[$pageClass] = + "(Title LIKE '%$keywords%' OR MenuTitle LIKE '%$keywords%' OR Content LIKE '%$keywords%'" + . " OR MetaDescription LIKE '%$keywords%' OR Title LIKE '%$htmlEntityKeywords%'" + . " OR MenuTitle LIKE '%$htmlEntityKeywords%' OR Content LIKE '%$htmlEntityKeywords%'" + . " OR MetaDescription LIKE '%$htmlEntityKeywords%')"; $fileClassSQL = Convert::raw2sql($fileClass); - $match[$fileClass] = "(Name LIKE '%$keywords%' OR Title LIKE '%$keywords%') AND ClassName = '$fileClassSQL'"; + $match[$fileClass] = + "(Name LIKE '%$keywords%' OR Title LIKE '%$keywords%') AND ClassName = '$fileClassSQL'"; // We make the relevance search by converting a boolean mode search into a normal one $relevanceKeywords = $keywords; $htmlEntityRelevanceKeywords = $htmlEntityKeywords; - $relevance[$pageClass] = "(Title LIKE '%$relevanceKeywords%' OR MenuTitle LIKE '%$relevanceKeywords%' OR Content LIKE '%$relevanceKeywords%' OR MetaDescription LIKE '%$relevanceKeywords%') + (Title LIKE '%$htmlEntityRelevanceKeywords%' OR MenuTitle LIKE '%$htmlEntityRelevanceKeywords%' OR Content LIKE '%$htmlEntityRelevanceKeywords%' OR MetaDescription LIKE '%$htmlEntityRelevanceKeywords%')"; + $relevance[$pageClass] = + "(Title LIKE '%$relevanceKeywords%' OR MenuTitle LIKE '%$relevanceKeywords%'" + . " OR Content LIKE '%$relevanceKeywords%' OR MetaDescription LIKE '%$relevanceKeywords%')" + . " + (Title LIKE '%$htmlEntityRelevanceKeywords%' OR MenuTitle LIKE '%$htmlEntityRelevanceKeywords%'" + . " OR Content LIKE '%$htmlEntityRelevanceKeywords%' OR MetaDescriptio " + . " LIKE '%$htmlEntityRelevanceKeywords%')"; $relevance[$fileClass] = "(Name LIKE '%$relevanceKeywords%' OR Title LIKE '%$relevanceKeywords%')"; } else { $relevance[$pageClass] = $relevance[$fileClass] = 1; @@ -464,7 +490,12 @@ class SQLite3Database extends Database $this->query("DELETE FROM \"$table\""); } - public function comparisonClause($field, $value, $exact = false, $negate = false, $caseSensitive = null, + public function comparisonClause( + $field, + $value, + $exact = false, + $negate = false, + $caseSensitive = null, $parameterised = false ) { if ($exact && !$caseSensitive) { diff --git a/code/SQLite3SchemaManager.php b/code/SQLite3SchemaManager.php index a494f40..91c47eb 100644 --- a/code/SQLite3SchemaManager.php +++ b/code/SQLite3SchemaManager.php @@ -73,7 +73,6 @@ class SQLite3SchemaManager extends DBSchemaManager $databases = array(); if ($files !== false) { foreach ($files as $file) { - // Filter non-files if (!is_file("$directory/$file")) { continue; @@ -172,8 +171,14 @@ class SQLite3SchemaManager extends DBSchemaManager return $table; } - public function alterTable($tableName, $newFields = null, $newIndexes = null, $alteredFields = null, - $alteredIndexes = null, $alteredOptions = null, $advancedOptions = null + public function alterTable( + $tableName, + $newFields = null, + $newIndexes = null, + $alteredFields = null, + $alteredIndexes = null, + $alteredOptions = null, + $advancedOptions = null ) { if ($newFields) { foreach ($newFields as $fieldName => $fieldSpec) { @@ -310,10 +315,12 @@ class SQLite3SchemaManager extends DBSchemaManager } // SQLite doesn't support direct renames through ALTER TABLE + $oldColsStr = implode(',', $oldCols); + $newColsSpecStr = implode(',', $newColsSpec); $queries = array( "BEGIN TRANSACTION", - "CREATE TABLE \"{$tableName}_renamefield_{$oldName}\" (" . implode(',', $newColsSpec) . ")", - "INSERT INTO \"{$tableName}_renamefield_{$oldName}\" SELECT " . implode(',', $oldCols) . " FROM \"$tableName\"", + "CREATE TABLE \"{$tableName}_renamefield_{$oldName}\" ({$newColsSpecStr})", + "INSERT INTO \"{$tableName}_renamefield_{$oldName}\" SELECT {$oldColsStr} FROM \"$tableName\"", "DROP TABLE \"$tableName\"", "ALTER TABLE \"{$tableName}_renamefield_{$oldName}\" RENAME TO \"$tableName\"", "COMMIT" @@ -329,9 +336,24 @@ class SQLite3SchemaManager extends DBSchemaManager // Recreate the indexes foreach ($oldIndexList as $indexName => $indexSpec) { - // Rename columns to new columns - $indexSpec['value'] = preg_replace("/\"$oldName\"/i", "\"$newName\"", $indexSpec['value']); - $this->createIndex($tableName, $indexName, $indexSpec); + // Map index columns + $columns = array_filter(array_map(function ($column) use ($newName, $oldName) { + // Unchanged + if ($column !== $oldName) { + return $column; + } + // Skip obsolete fields + if (stripos($newName, '_obsolete_') === 0) { + return null; + } + return $newName; + }, $indexSpec['columns'])); + + // Create index if column count unchanged + if (count($columns) === count($indexSpec['columns'])) { + $indexSpec['columns'] = $columns; + $this->createIndex($tableName, $indexName, $indexSpec); + } } } @@ -344,8 +366,10 @@ class SQLite3SchemaManager extends DBSchemaManager $fieldList = array(); if ($sqlCreate && $sqlCreate['sql']) { - preg_match('/^[\s]*CREATE[\s]+TABLE[\s]+[\'"]?[a-zA-Z0-9_\\\]+[\'"]?[\s]*\((.+)\)[\s]*$/ims', - $sqlCreate['sql'], $matches + preg_match( + '/^[\s]*CREATE[\s]+TABLE[\s]+[\'"]?[a-zA-Z0-9_\\\]+[\'"]?[\s]*\((.+)\)[\s]*$/ims', + $sqlCreate['sql'], + $matches ); $fields = isset($matches[1]) ? preg_split('/,(?=(?:[^\'"]*$)|(?:[^\'"]*[\'"][^\'"]*[\'"][^\'"]*)*$)/x', $matches[1]) @@ -411,7 +435,6 @@ class SQLite3SchemaManager extends DBSchemaManager // Enumerate each index and related fields foreach ($this->query("PRAGMA index_list(\"$table\")") as $index) { - // The SQLite internal index name, not the actual Silverstripe name $indexName = $index["name"]; $indexType = $index['unique'] ? 'unique' : 'index'; @@ -501,7 +524,9 @@ class SQLite3SchemaManager extends DBSchemaManager // Ensure the cache table exists if (empty($this->enum_map)) { - $this->query("CREATE TABLE IF NOT EXISTS \"SQLiteEnums\" (\"TableColumn\" TEXT PRIMARY KEY, \"EnumList\" TEXT)"); + $this->query( + "CREATE TABLE IF NOT EXISTS \"SQLiteEnums\" (\"TableColumn\" TEXT PRIMARY KEY, \"EnumList\" TEXT)" + ); } // Ensure the table row exists diff --git a/code/SQLiteDatabaseConfigurationHelper.php b/code/SQLiteDatabaseConfigurationHelper.php index 9376d15..a9293c4 100644 --- a/code/SQLiteDatabaseConfigurationHelper.php +++ b/code/SQLiteDatabaseConfigurationHelper.php @@ -41,7 +41,11 @@ class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper if (empty($databaseConfig['key'])) { $conn = @new SQLite3($file, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE); } else { - $conn = @new SQLite3($file, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, $databaseConfig['key']); + $conn = @new SQLite3( + $file, + SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE, + $databaseConfig['key'] + ); } break; case 'SQLite3PDODatabase': @@ -106,15 +110,15 @@ class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper // Do additional validation around file paths if (empty($databaseConfig['path'])) { return array( - 'success' => false, - 'error' => "Missing directory path" - ); + 'success' => false, + 'error' => "Missing directory path" + ); } if (empty($databaseConfig['database'])) { return array( - 'success' => false, - 'error' => "Missing database filename" - ); + 'success' => false, + 'error' => "Missing database filename" + ); } // Create and secure db directory @@ -122,16 +126,16 @@ class SQLiteDatabaseConfigurationHelper implements DatabaseConfigurationHelper $dirCreated = self::create_db_dir($path); if (!$dirCreated) { return array( - 'success' => false, - 'error' => sprintf('Cannot create path: "%s"', $path) - ); + 'success' => false, + 'error' => sprintf('Cannot create path: "%s"', $path) + ); } $dirSecured = self::secure_db_dir($path); if (!$dirSecured) { return array( - 'success' => false, - 'error' => sprintf('Cannot secure path through .htaccess: "%s"', $path) - ); + 'success' => false, + 'error' => sprintf('Cannot secure path through .htaccess: "%s"', $path) + ); } $conn = $this->createConnection($databaseConfig, $error); diff --git a/composer.json b/composer.json index cea94c9..0d39efc 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,10 @@ "SilverStripe\\SQLite\\": "code/" } }, + "scripts": { + "lint": "phpcs code/ *.php", + "lint-clean": "phpcbf code/ *.php" + }, "minimum-stability": "dev", "prefer-stable": true } diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 0000000..e29db79 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,11 @@ + + + CodeSniffer ruleset for SilverStripe coding conventions. + + + + + + + +