aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.drone.yml21
-rw-r--r--apps/files/css/files.css6
-rw-r--r--apps/files/js/filelist.js22
-rwxr-xr-xautotest.sh44
-rw-r--r--config/config.sample.php29
-rw-r--r--core/css/icons.css7
-rw-r--r--core/css/inputs.css2
-rw-r--r--core/img/actions/fullscreen-white.svg4
-rw-r--r--core/img/actions/fullscreen.svg4
-rw-r--r--core/js/compatibility.js155
-rw-r--r--core/js/core.json1
-rw-r--r--core/js/js.js193
-rw-r--r--core/js/octemplate.js2
-rw-r--r--core/js/placeholders.js459
-rw-r--r--core/register_command.php2
-rw-r--r--lib/private/DB/AdapterMySQL.php14
-rw-r--r--lib/private/DB/ConnectionFactory.php11
-rw-r--r--lib/private/DB/MDB2SchemaReader.php14
-rw-r--r--lib/private/DB/MDB2SchemaWriter.php6
-rw-r--r--lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php19
-rw-r--r--lib/private/Repair.php3
-rw-r--r--lib/private/Repair/Collation.php45
-rw-r--r--lib/private/Server.php2
-rw-r--r--lib/private/Setup/AbstractDatabase.php2
-rw-r--r--lib/private/Setup/MySQL.php5
-rw-r--r--lib/private/legacy/template.php2
-rw-r--r--tests/data/db_structure.xml15
-rw-r--r--tests/docker/mysqlmb4.config.php5
-rw-r--r--tests/docker/mysqlmb4/mb4.cnf5
-rw-r--r--tests/lib/DB/LegacyDBTest.php35
-rw-r--r--tests/lib/Files/Cache/CacheTest.php3
-rw-r--r--tests/lib/Repair/RepairCollationTest.php16
32 files changed, 306 insertions, 847 deletions
diff --git a/.drone.yml b/.drone.yml
index c54907cc5d9..78cdefd82b4 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -148,6 +148,14 @@ pipeline:
matrix:
DB: postgres
PHP: 5.6
+ mysqlmb4-php5.6:
+ image: nextcloudci/php5.6:php5.6-2
+ commands:
+ - NOCOVERAGE=true TEST_SELECTION=DB ./autotest.sh mysqlmb4
+ when:
+ matrix:
+ DB: mysqlmb4
+ PHP: 5.6
integration-capabilities_features:
image: nextcloudci/integration-php7.0:integration-php7.0-1
commands:
@@ -368,6 +376,8 @@ matrix:
PHP: 5.6
- DB: postgres
PHP: 5.6
+ - DB: mysqlmb4
+ PHP: 5.6
services:
cache:
@@ -390,3 +400,14 @@ services:
when:
matrix:
DB: mysql
+ mysqlmb4:
+ image: mysql
+ environment:
+ - MYSQL_ROOT_PASSWORD=owncloud
+ - MYSQL_USER=oc_autotest
+ - MYSQL_PASSWORD=owncloud
+ - MYSQL_DATABASE=oc_autotest
+ command: [ "--innodb_large_prefix=true", "--innodb_file_format=barracuda", "--innodb_file_per_table=true" ]
+ when:
+ matrix:
+ DB: mysqlmb4
diff --git a/apps/files/css/files.css b/apps/files/css/files.css
index acd7f4af25d..9b844919c4e 100644
--- a/apps/files/css/files.css
+++ b/apps/files/css/files.css
@@ -148,10 +148,10 @@ table tr.mouseOver td {
}
tbody a { color:#000; }
-span.extension, span.uploading, td.date {
+span.conflict-path, span.extension, span.uploading, td.date {
color: #999;
}
-span.extension {
+span.conflict-path, span.extension {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)";
filter: alpha(opacity=70);
opacity: .7;
@@ -161,6 +161,8 @@ span.extension {
transition: opacity 300ms;
vertical-align: top;
}
+tr:hover span.conflict-path,
+tr:focus span.conflict-path,
tr:hover span.extension,
tr:focus span.extension {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 896af1dd6e5..159d008e6e6 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -1148,6 +1148,28 @@
}
var nameSpan=$('<span></span>').addClass('nametext');
var innernameSpan = $('<span></span>').addClass('innernametext').text(basename);
+
+ if (path && path !== '/') {
+ var conflictingItems = this.$fileList.find('tr[data-file="' + name.replace( /(:|\.|\[|\]|,|=)/g, "\\$1") + '"]');
+ if (conflictingItems.length !== 0) {
+ if (conflictingItems.length === 1) {
+ // Update the path on the first conflicting item
+ var $firstConflict = $(conflictingItems[0]),
+ firstConflictPath = $firstConflict.attr('data-path') + '/';
+ if (firstConflictPath.charAt(0) === '/') {
+ firstConflictPath = firstConflictPath.substr(1);
+ }
+ $firstConflict.find('td.filename span.innernametext').prepend($('<span></span>').addClass('conflict-path').text(firstConflictPath));
+ }
+
+ var conflictPath = path + '/';
+ if (conflictPath.charAt(0) === '/') {
+ conflictPath = conflictPath.substr(1);
+ }
+ nameSpan.append($('<span></span>').addClass('conflict-path').text(conflictPath));
+ }
+ }
+
nameSpan.append(innernameSpan);
linkElem.append(nameSpan);
if (extension) {
diff --git a/autotest.sh b/autotest.sh
index eca3d81c048..61e7426ab99 100755
--- a/autotest.sh
+++ b/autotest.sh
@@ -21,7 +21,7 @@ ADMINLOGIN=admin$EXECUTOR_NUMBER
BASEDIR=$PWD
PRIMARY_STORAGE_CONFIGS="local swift"
-DBCONFIGS="sqlite mysql mariadb pgsql oci"
+DBCONFIGS="sqlite mysql mariadb pgsql oci mysqlmb4"
# $PHP_EXE is run through 'which' and as such e.g. 'php' or 'hhvm' is usually
# sufficient. Due to the behaviour of 'which', $PHP_EXE may also be a path
@@ -209,6 +209,48 @@ function execute_tests {
exit 1
fi
fi
+ if [ "$DB" == "mysqlmb4" ] ; then
+ if [ ! -z "$USEDOCKER" ] ; then
+ echo "Fire up the mysql docker"
+ DOCKER_CONTAINER_ID=$(docker run \
+ -v $BASEDIR/tests/docker/mysqlmb4:/etc/mysql/conf.d \
+ -e MYSQL_ROOT_PASSWORD=owncloud \
+ -e MYSQL_USER="$DATABASEUSER" \
+ -e MYSQL_PASSWORD=owncloud \
+ -e MYSQL_DATABASE="$DATABASENAME" \
+ -d mysql:5.7
+ --innodb_large_prefix=true
+ --innodb_file_format=barracuda
+ --innodb_file_per_table=true)
+
+ DATABASEHOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "$DOCKER_CONTAINER_ID")
+
+ else
+ if [ -z "$DRONE" ] ; then # no need to drop the DB when we are on CI
+ if [ "mysql" != "$(mysql --version | grep -o mysql)" ] ; then
+ echo "Your mysql binary is not provided by mysql"
+ echo "To use the docker container set the USEDOCKER environment variable"
+ exit -1
+ fi
+ mysql -u "$DATABASEUSER" -powncloud -e "DROP DATABASE IF EXISTS $DATABASENAME" -h $DATABASEHOST || true
+ else
+ DATABASEHOST=127.0.0.1
+ fi
+ fi
+
+ echo "Waiting for MySQL(utf8mb4) initialisation ..."
+
+ if ! apps/files_external/tests/env/wait-for-connection $DATABASEHOST 3306 60; then
+ echo "[ERROR] Waited 60 seconds, no response" >&2
+ exit 1
+ fi
+ sleep 1
+
+ echo "MySQL(utf8mb4) is up."
+ _DB="mysql"
+
+ cp tests/docker/mysqlmb4.config.php config
+ fi
if [ "$DB" == "mariadb" ] ; then
if [ ! -z "$USEDOCKER" ] ; then
echo "Fire up the mariadb docker"
diff --git a/config/config.sample.php b/config/config.sample.php
index 3aa0f353c59..df1e2d16fc0 100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -129,6 +129,7 @@ $CONFIG = array(
*/
'dbtableprefix' => '',
+
/**
* Indicates whether the Nextcloud instance was installed successfully; ``true``
* indicates a successful installation, and ``false`` indicates an unsuccessful
@@ -1080,6 +1081,34 @@ $CONFIG = array(
'sqlite.journal_mode' => 'DELETE',
/**
+ * If this setting is set to true MySQL can handle 4 byte characters instead of
+ * 3 byte characters
+ *
+ * MySQL requires a special setup for longer indexes (> 767 bytes) which are
+ * needed:
+ *
+ * [mysqld]
+ * innodb_large_prefix=true
+ * innodb_file_format=barracuda
+ * innodb_file_per_table=true
+ *
+ * Tables will be created with
+ * * character set: utf8mb4
+ * * collation: utf8mb4_bin
+ * * row_format: compressed
+ *
+ * See:
+ * https://dev.mysql.com/doc/refman/5.7/en/charset-unicode-utf8mb4.html
+ * https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_large_prefix
+ * https://mariadb.com/kb/en/mariadb/xtradbinnodb-server-system-variables/#innodb_large_prefix
+ * http://www.tocker.ca/2013/10/31/benchmarking-innodb-page-compression-performance.html
+ * http://mechanics.flite.com/blog/2014/07/29/using-innodb-large-prefix-to-avoid-error-1071/
+ *
+ * WARNING: EXPERIMENTAL
+ */
+'mysql.utf8mb4' => false,
+
+/**
* Database types that are supported for installation.
*
* Available:
diff --git a/core/css/icons.css b/core/css/icons.css
index df36ae58710..d62ab1504db 100644
--- a/core/css/icons.css
+++ b/core/css/icons.css
@@ -195,6 +195,13 @@ img.icon-loading-small-dark, object.icon-loading-small-dark, video.icon-loading-
background-image: url('../img/actions/external.svg?v=1');
}
+.icon-fullscreen {
+ background-image: url('../img/actions/fullscreen.svg?v=1');
+}
+.icon-fullscreen-white {
+ background-image: url('../img/actions/fullscreen-white.svg?v=1');
+}
+
.icon-history {
background-image: url('../img/actions/history.svg?v=1');
}
diff --git a/core/css/inputs.css b/core/css/inputs.css
index a79f72cfd14..37fedb9a44e 100644
--- a/core/css/inputs.css
+++ b/core/css/inputs.css
@@ -429,7 +429,7 @@ input:disabled+label, input:disabled:hover+label, input:disabled:focus+label {
.primary, input[type="submit"].primary, input[type="button"].primary, button.primary, .button.primary {
border: 1px solid #0082c9;
background-color: #00a2e9;
- color: #ddd;
+ color: #fff;
}
.primary:hover, input[type="submit"].primary:hover, input[type="button"].primary:hover, button.primary:hover, .button.primary:hover,
.primary:focus, input[type="submit"].primary:focus, input[type="button"].primary:focus, button.primary:focus, .button.primary:focus {
diff --git a/core/img/actions/fullscreen-white.svg b/core/img/actions/fullscreen-white.svg
new file mode 100644
index 00000000000..77052945fb2
--- /dev/null
+++ b/core/img/actions/fullscreen-white.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewBox="0 0 16 16">
+ <path d="m8 1c-0.554 0-1 0.446-1 1s0.446 1 1 1h5v5c0 0.554 0.446 1 1 1s1-0.446 1-1v-6c0-0.554-0.446-1-1-1h-6zm-6 6c-0.554 0-1 0.446-1 1v6c0 0.554 0.446 1 1 1h6c0.554 0 1-0.446 1-1s-0.446-1-1-1h-5v-5c0-0.554-0.446-1-1-1z" fill-rule="evenodd" fill="#fff"/>
+</svg>
diff --git a/core/img/actions/fullscreen.svg b/core/img/actions/fullscreen.svg
new file mode 100644
index 00000000000..8bf215779db
--- /dev/null
+++ b/core/img/actions/fullscreen.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewBox="0 0 16 16">
+ <path d="m8 1c-0.554 0-1 0.446-1 1s0.446 1 1 1h5v5c0 0.554 0.446 1 1 1s1-0.446 1-1v-6c0-0.554-0.446-1-1-1h-6zm-6 6c-0.554 0-1 0.446-1 1v6c0 0.554 0.446 1 1 1h6c0.554 0 1-0.446 1-1s-0.446-1-1-1h-5v-5c0-0.554-0.446-1-1-1z" fill-rule="evenodd"/>
+</svg>
diff --git a/core/js/compatibility.js b/core/js/compatibility.js
deleted file mode 100644
index ac942d202e8..00000000000
--- a/core/js/compatibility.js
+++ /dev/null
@@ -1,155 +0,0 @@
-/**
- * implement Object.create for browsers without native support
- */
-if (typeof Object.create !== 'function') {
- Object.create = function (o) {
- function F() {}
- F.prototype = o;
- return new F();
- };
-}
-
-/**
- * implement Object.keys for browsers without native support
- */
-if (typeof Object.keys !== 'function') {
- Object.keys = function(o) {
- if (o !== Object(o)) {
- throw new TypeError('Object.keys called on a non-object');
- }
- var k=[],p;
- for (p in o) {
- if (Object.prototype.hasOwnProperty.call(o,p)) {
- k.push(p);
- }
- }
- return k;
- };
-}
-
-/**
- * implement Array.filter for browsers without native support
- */
-if (!Array.prototype.filter) {
- Array.prototype.filter = function(fun /*, thisp*/) {
- var len = this.length >>> 0;
- if (typeof fun !== "function"){
- throw new TypeError();
- }
-
- var res = [];
- var thisp = arguments[1];
- for (var i = 0; i < len; i++) {
- if (i in this) {
- var val = this[i]; // in case fun mutates this
- if (fun.call(thisp, val, i, this)) {
- res.push(val);
- }
- }
- }
- return res;
- };
-}
-
-/**
- * implement Array.indexOf for browsers without native support
- */
-if (!Array.prototype.indexOf){
- Array.prototype.indexOf = function(elt /*, from*/)
- {
- var len = this.length;
-
- var from = Number(arguments[1]) || 0;
- from = (from < 0) ? Math.ceil(from) : Math.floor(from);
- if (from < 0){
- from += len;
- }
-
- for (; from < len; from++)
- {
- if (from in this && this[from] === elt){
- return from;
- }
- }
- return -1;
- };
-}
-
-/**
- * implement Array.map for browsers without native support
- */
-if (!Array.prototype.map){
- Array.prototype.map = function(fun /*, thisp */){
- "use strict";
-
- if (this === void 0 || this === null){
- throw new TypeError();
- }
-
- var t = Object(this);
- var len = t.length >>> 0;
- if (typeof fun !== "function"){
- throw new TypeError();
- }
-
- var res = new Array(len);
- var thisp = arguments[1];
- for (var i = 0; i < len; i++){
- if (i in t){
- res[i] = fun.call(thisp, t[i], i, t);
- }
- }
-
- return res;
- };
-}
-
-//https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
-if (!Function.prototype.bind) {
- Function.prototype.bind = function (oThis) {
- if (typeof this !== "function") {
- // closest thing possible to the ECMAScript 5 internal IsCallable function
- throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
- }
-
- var aArgs = Array.prototype.slice.call(arguments, 1),
- fToBind = this,
- fNOP = function () {},
- fBound = function () {
- return fToBind.apply(this instanceof fNOP && oThis
- ? this
- : oThis,
- aArgs.concat(Array.prototype.slice.call(arguments)));
- };
-
- fNOP.prototype = this.prototype;
- fBound.prototype = new fNOP();
-
- return fBound;
- };
-}
-
-//https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/Trim
-if(!String.prototype.trim) {
- String.prototype.trim = function () {
- return this.replace(/^\s+|\s+$/g,'');
- };
-}
-
-// Older Firefoxes doesn't support outerHTML
-// From http://stackoverflow.com/questions/1700870/how-do-i-do-outerhtml-in-firefox#answer-3819589
-function outerHTML(node){
- // In newer browsers use the internal property otherwise build a wrapper.
- return node.outerHTML || (
- function(n){
- var div = document.createElement('div'), h;
- div.appendChild( n.cloneNode(true) );
- h = div.innerHTML;
- div = null;
- return h;
- })(node);
-}
-
-// devicePixelRatio for IE10
-window.devicePixelRatio = window.devicePixelRatio ||
- window.screen.deviceXDPI / window.screen.logicalXDPI || 1;
diff --git a/core/js/core.json b/core/js/core.json
index e651c9d7597..c98928d0fed 100644
--- a/core/js/core.json
+++ b/core/js/core.json
@@ -20,7 +20,6 @@
"placeholder.js"
],
"modules": [
- "compatibility.js",
"jquery.ocdialog.js",
"oc-dialogs.js",
"js.js",
diff --git a/core/js/js.js b/core/js/js.js
index 4b09cc49038..16da273c8e1 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -1189,199 +1189,6 @@ OC.Notification={
};
/**
- * Breadcrumb class
- *
- * @namespace
- *
- * @deprecated will be replaced by the breadcrumb implementation
- * of the files app in the future
- */
-OC.Breadcrumb={
- container:null,
- /**
- * @todo Write documentation
- * @param dir
- * @param leafName
- * @param leafLink
- */
- show:function(dir, leafName, leafLink){
- if(!this.container){//default
- this.container=$('#controls');
- }
- this._show(this.container, dir, leafName, leafLink);
- },
- _show:function(container, dir, leafname, leaflink){
- var self = this;
-
- this._clear(container);
-
- // show home + path in subdirectories
- if (dir) {
- //add home
- var link = OC.linkTo('files','index.php');
-
- var crumb=$('<div/>');
- crumb.addClass('crumb');
-
- var crumbLink=$('<a/>');
- crumbLink.attr('href',link);
-
- var crumbImg=$('<img/>');
- crumbImg.attr('src',OC.imagePath('core','places/home'));
- crumbLink.append(crumbImg);
- crumb.append(crumbLink);
- container.prepend(crumb);
-
- //add path parts
- var segments = dir.split('/');
- var pathurl = '';
- jQuery.each(segments, function(i,name) {
- if (name !== '') {
- pathurl = pathurl+'/'+name;
- var link = OC.linkTo('files','index.php')+'?dir='+encodeURIComponent(pathurl);
- self._push(container, name, link);
- }
- });
- }
-
- //add leafname
- if (leafname && leaflink) {
- this._push(container, leafname, leaflink);
- }
- },
-
- /**
- * @todo Write documentation
- * @param {string} name
- * @param {string} link
- */
- push:function(name, link){
- if(!this.container){//default
- this.container=$('#controls');
- }
- return this._push(OC.Breadcrumb.container, name, link);
- },
- _push:function(container, name, link){
- var crumb=$('<div/>');
- crumb.addClass('crumb').addClass('last');
-
- var crumbLink=$('<a/>');
- crumbLink.attr('href',link);
- crumbLink.text(name);
- crumb.append(crumbLink);
-
- var existing=container.find('div.crumb');
- if(existing.length){
- existing.removeClass('last');
- existing.last().after(crumb);
- }else{
- container.prepend(crumb);
- }
- return crumb;
- },
-
- /**
- * @todo Write documentation
- */
- pop:function(){
- if(!this.container){//default
- this.container=$('#controls');
- }
- this.container.find('div.crumb').last().remove();
- this.container.find('div.crumb').last().addClass('last');
- },
-
- /**
- * @todo Write documentation
- */
- clear:function(){
- if(!this.container){//default
- this.container=$('#controls');
- }
- this._clear(this.container);
- },
- _clear:function(container) {
- container.find('div.crumb').remove();
- }
-};
-
-if(typeof localStorage !=='undefined' && localStorage !== null){
- /**
- * User and instance aware localstorage
- * @namespace
- */
- OC.localStorage={
- namespace:'oc_'+OC.currentUser+'_'+OC.webroot+'_',
-
- /**
- * Whether the storage contains items
- * @param {string} name
- * @return {boolean}
- */
- hasItem:function(name){
- return OC.localStorage.getItem(name)!==null;
- },
-
- /**
- * Add an item to the storage
- * @param {string} name
- * @param {string} item
- */
- setItem:function(name,item){
- return localStorage.setItem(OC.localStorage.namespace+name,JSON.stringify(item));
- },
-
- /**
- * Removes an item from the storage
- * @param {string} name
- * @param {string} item
- */
- removeItem:function(name,item){
- return localStorage.removeItem(OC.localStorage.namespace+name);
- },
-
- /**
- * Get an item from the storage
- * @param {string} name
- * @return {null|string}
- */
- getItem:function(name){
- var item = localStorage.getItem(OC.localStorage.namespace+name);
- if(item === null) {
- return null;
- } else {
- return JSON.parse(item);
- }
- }
- };
-}else{
- //dummy localstorage
- OC.localStorage={
- hasItem:function(){
- return false;
- },
- setItem:function(){
- return false;
- },
- getItem:function(){
- return null;
- }
- };
-}
-
-/**
- * prototypical inheritance functions
- * @todo Write documentation
- * usage:
- * MySubObject=object(MyObject)
- */
-function object(o) {
- function F() {}
- F.prototype = o;
- return new F();
-}
-
-/**
* Initializes core
*/
function initCore() {
diff --git a/core/js/octemplate.js b/core/js/octemplate.js
index 67aa7d69cce..b24ad95c2b0 100644
--- a/core/js/octemplate.js
+++ b/core/js/octemplate.js
@@ -76,7 +76,7 @@
},
// From stackoverflow.com/questions/1408289/best-way-to-do-variable-interpolation-in-javascript
_build: function(o){
- var data = this.elem.attr('type') === 'text/template' ? this.elem.html() : outerHTML(this.elem.get(0));
+ var data = this.elem.attr('type') === 'text/template' ? this.elem.html() : this.elem.get(0).outerHTML;
try {
return data.replace(/{([^{}]*)}/g,
function (a, b) {
diff --git a/core/js/placeholders.js b/core/js/placeholders.js
deleted file mode 100644
index e63f429d40f..00000000000
--- a/core/js/placeholders.js
+++ /dev/null
@@ -1,459 +0,0 @@
-/*
- * The MIT License
- *
- * Copyright (c) 2012 James Allardice
- *
- * 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.
- */
-
-// Defines the global Placeholders object along with various utility methods
-(function (global) {
-
- "use strict";
-
- // Cross-browser DOM event binding
- function addEventListener(elem, event, fn) {
- if (elem.addEventListener) {
- return elem.addEventListener(event, fn, false);
- }
- if (elem.attachEvent) {
- return elem.attachEvent("on" + event, fn);
- }
- }
-
- // Check whether an item is in an array (we don't use Array.prototype.indexOf so we don't clobber any existing polyfills - this is a really simple alternative)
- function inArray(arr, item) {
- var i, len;
- for (i = 0, len = arr.length; i < len; i++) {
- if (arr[i] === item) {
- return true;
- }
- }
- return false;
- }
-
- // Move the caret to the index position specified. Assumes that the element has focus
- function moveCaret(elem, index) {
- var range;
- if (elem.createTextRange) {
- range = elem.createTextRange();
- range.move("character", index);
- range.select();
- } else if (elem.selectionStart) {
- elem.focus();
- elem.setSelectionRange(index, index);
- }
- }
-
- // Attempt to change the type property of an input element
- function changeType(elem, type) {
- try {
- elem.type = type;
- return true;
- } catch (e) {
- // You can't change input type in IE8 and below
- return false;
- }
- }
-
- // Expose public methods
- global.Placeholders = {
- Utils: {
- addEventListener: addEventListener,
- inArray: inArray,
- moveCaret: moveCaret,
- changeType: changeType
- }
- };
-
-}(this));
-
-(function (global) {
-
- "use strict";
-
- var validTypes = [
- "text",
- "search",
- "url",
- "tel",
- "email",
- "password",
- "number",
- "textarea"
- ],
-
- // The list of keycodes that are not allowed when the polyfill is configured to hide-on-input
- badKeys = [
-
- // The following keys all cause the caret to jump to the end of the input value
- 27, // Escape
- 33, // Page up
- 34, // Page down
- 35, // End
- 36, // Home
-
- // Arrow keys allow you to move the caret manually, which should be prevented when the placeholder is visible
- 37, // Left
- 38, // Up
- 39, // Right
- 40, // Down
-
- // The following keys allow you to modify the placeholder text by removing characters, which should be prevented when the placeholder is visible
- 8, // Backspace
- 46 // Delete
- ],
-
- // Styling variables
- placeholderStyleColor = "#ccc",
- placeholderClassName = "placeholdersjs",
- classNameRegExp = new RegExp("(?:^|\\s)" + placeholderClassName + "(?!\\S)"),
-
- // These will hold references to all elements that can be affected. NodeList objects are live, so we only need to get those references once
- inputs, textareas,
-
- // The various data-* attributes used by the polyfill
- ATTR_CURRENT_VAL = "data-placeholder-value",
- ATTR_ACTIVE = "data-placeholder-active",
- ATTR_INPUT_TYPE = "data-placeholder-type",
- ATTR_FORM_HANDLED = "data-placeholder-submit",
- ATTR_EVENTS_BOUND = "data-placeholder-bound",
- ATTR_OPTION_FOCUS = "data-placeholder-focus",
- ATTR_OPTION_LIVE = "data-placeholder-live",
- ATTR_MAXLENGTH = "data-placeholder-maxlength",
-
- // Various other variables used throughout the rest of the script
- test = document.createElement("input"),
- head = document.getElementsByTagName("head")[0],
- root = document.documentElement,
- Placeholders = global.Placeholders,
- Utils = Placeholders.Utils,
- hideOnInput, liveUpdates, keydownVal, styleElem, styleRules, placeholder, timer, form, elem, len, i;
-
- // No-op (used in place of public methods when native support is detected)
- function noop() {}
-
- // Avoid IE9 activeElement of death when an iframe is used.
- // More info:
- // http://bugs.jquery.com/ticket/13393
- // https://github.com/jquery/jquery/commit/85fc5878b3c6af73f42d61eedf73013e7faae408
- function safeActiveElement() {
- try {
- return document.activeElement;
- } catch (err) {}
- }
-
- // Hide the placeholder value on a single element. Returns true if the placeholder was hidden and false if it was not (because it wasn't visible in the first place)
- function hidePlaceholder(elem, keydownValue) {
- var type,
- maxLength,
- valueChanged = (!!keydownValue && elem.value !== keydownValue),
- isPlaceholderValue = (elem.value === elem.getAttribute(ATTR_CURRENT_VAL));
-
- if ((valueChanged || isPlaceholderValue) && elem.getAttribute(ATTR_ACTIVE) === "true") {
- elem.removeAttribute(ATTR_ACTIVE);
- elem.value = elem.value.replace(elem.getAttribute(ATTR_CURRENT_VAL), "");
- elem.className = elem.className.replace(classNameRegExp, "");
-
- // Restore the maxlength value
- maxLength = elem.getAttribute(ATTR_MAXLENGTH);
- if (parseInt(maxLength, 10) >= 0) { // Old FF returns -1 if attribute not set (see GH-56)
- elem.setAttribute("maxLength", maxLength);
- elem.removeAttribute(ATTR_MAXLENGTH);
- }
-
- // If the polyfill has changed the type of the element we need to change it back
- type = elem.getAttribute(ATTR_INPUT_TYPE);
- if (type) {
- elem.type = type;
- }
- return true;
- }
- return false;
- }
-
- // Show the placeholder value on a single element. Returns true if the placeholder was shown and false if it was not (because it was already visible)
- function showPlaceholder(elem) {
- var type,
- maxLength,
- val = elem.getAttribute(ATTR_CURRENT_VAL);
- if (elem.value === "" && val) {
- elem.setAttribute(ATTR_ACTIVE, "true");
- elem.value = val;
- elem.className += " " + placeholderClassName;
-
- // Store and remove the maxlength value
- maxLength = elem.getAttribute(ATTR_MAXLENGTH);
- if (!maxLength) {
- elem.setAttribute(ATTR_MAXLENGTH, elem.maxLength);
- elem.removeAttribute("maxLength");
- }
-
- // If the type of element needs to change, change it (e.g. password inputs)
- type = elem.getAttribute(ATTR_INPUT_TYPE);
- if (type) {
- elem.type = "text";
- } else if (elem.type === "password") {
- if (Utils.changeType(elem, "text")) {
- elem.setAttribute(ATTR_INPUT_TYPE, "password");
- }
- }
- return true;
- }
- return false;
- }
-
- function handleElem(node, callback) {
-
- var handleInputsLength, handleTextareasLength, handleInputs, handleTextareas, elem, len, i;
-
- // Check if the passed in node is an input/textarea (in which case it can't have any affected descendants)
- if (node && node.getAttribute(ATTR_CURRENT_VAL)) {
- callback(node);
- } else {
-
- // If an element was passed in, get all affected descendants. Otherwise, get all affected elements in document
- handleInputs = node ? node.getElementsByTagName("input") : inputs;
- handleTextareas = node ? node.getElementsByTagName("textarea") : textareas;
-
- handleInputsLength = handleInputs ? handleInputs.length : 0;
- handleTextareasLength = handleTextareas ? handleTextareas.length : 0;
-
- // Run the callback for each element
- for (i = 0, len = handleInputsLength + handleTextareasLength; i < len; i++) {
- elem = i < handleInputsLength ? handleInputs[i] : handleTextareas[i - handleInputsLength];
- callback(elem);
- }
- }
- }
-
- // Return all affected elements to their normal state (remove placeholder value if present)
- function disablePlaceholders(node) {
- handleElem(node, hidePlaceholder);
- }
-
- // Show the placeholder value on all appropriate elements
- function enablePlaceholders(node) {
- handleElem(node, showPlaceholder);
- }
-
- // Returns a function that is used as a focus event handler
- function makeFocusHandler(elem) {
- return function () {
-
- // Only hide the placeholder value if the (default) hide-on-focus behaviour is enabled
- if (hideOnInput && elem.value === elem.getAttribute(ATTR_CURRENT_VAL) && elem.getAttribute(ATTR_ACTIVE) === "true") {
-
- // Move the caret to the start of the input (this mimics the behaviour of all browsers that do not hide the placeholder on focus)
- Utils.moveCaret(elem, 0);
-
- } else {
-
- // Remove the placeholder
- hidePlaceholder(elem);
- }
- };
- }
-
- // Returns a function that is used as a blur event handler
- function makeBlurHandler(elem) {
- return function () {
- showPlaceholder(elem);
- };
- }
-
- // Functions that are used as a event handlers when the hide-on-input behaviour has been activated - very basic implementation of the "input" event
- function makeKeydownHandler(elem) {
- return function (e) {
- keydownVal = elem.value;
-
- //Prevent the use of the arrow keys (try to keep the cursor before the placeholder)
- if (elem.getAttribute(ATTR_ACTIVE) === "true") {
- if (keydownVal === elem.getAttribute(ATTR_CURRENT_VAL) && Utils.inArray(badKeys, e.keyCode)) {
- if (e.preventDefault) {
- e.preventDefault();
- }
- return false;
- }
- }
- };
- }
- function makeKeyupHandler(elem) {
- return function () {
- hidePlaceholder(elem, keydownVal);
-
- // If the element is now empty we need to show the placeholder
- if (elem.value === "") {
- elem.blur();
- Utils.moveCaret(elem, 0);
- }
- };
- }
- function makeClickHandler(elem) {
- return function () {
- if (elem === safeActiveElement() && elem.value === elem.getAttribute(ATTR_CURRENT_VAL) && elem.getAttribute(ATTR_ACTIVE) === "true") {
- Utils.moveCaret(elem, 0);
- }
- };
- }
-
- // Returns a function that is used as a submit event handler on form elements that have children affected by this polyfill
- function makeSubmitHandler(form) {
- return function () {
-
- // Turn off placeholders on all appropriate descendant elements
- disablePlaceholders(form);
- };
- }
-
- // Bind event handlers to an element that we need to affect with the polyfill
- function newElement(elem) {
-
- // If the element is part of a form, make sure the placeholder string is not submitted as a value
- if (elem.form) {
- form = elem.form;
-
- // If the type of the property is a string then we have a "form" attribute and need to get the real form
- if (typeof form === "string") {
- form = document.getElementById(form);
- }
-
- // Set a flag on the form so we know it's been handled (forms can contain multiple inputs)
- if (!form.getAttribute(ATTR_FORM_HANDLED)) {
- Utils.addEventListener(form, "submit", makeSubmitHandler(form));
- form.setAttribute(ATTR_FORM_HANDLED, "true");
- }
- }
-
- // Bind event handlers to the element so we can hide/show the placeholder as appropriate
- Utils.addEventListener(elem, "focus", makeFocusHandler(elem));
- Utils.addEventListener(elem, "blur", makeBlurHandler(elem));
-
- // If the placeholder should hide on input rather than on focus we need additional event handlers
- if (hideOnInput) {
- Utils.addEventListener(elem, "keydown", makeKeydownHandler(elem));
- Utils.addEventListener(elem, "keyup", makeKeyupHandler(elem));
- Utils.addEventListener(elem, "click", makeClickHandler(elem));
- }
-
- // Remember that we've bound event handlers to this element
- elem.setAttribute(ATTR_EVENTS_BOUND, "true");
- elem.setAttribute(ATTR_CURRENT_VAL, placeholder);
-
- // If the element doesn't have a value and is not focussed, set it to the placeholder string
- if (hideOnInput || elem !== safeActiveElement()) {
- showPlaceholder(elem);
- }
- }
-
- Placeholders.nativeSupport = test.placeholder !== void 0;
-
- if (!Placeholders.nativeSupport) {
-
- // Get references to all the input and textarea elements currently in the DOM (live NodeList objects to we only need to do this once)
- inputs = document.getElementsByTagName("input");
- textareas = document.getElementsByTagName("textarea");
-
- // Get any settings declared as data-* attributes on the root element (currently the only options are whether to hide the placeholder on focus or input and whether to auto-update)
- hideOnInput = root.getAttribute(ATTR_OPTION_FOCUS) === "false";
- liveUpdates = root.getAttribute(ATTR_OPTION_LIVE) !== "false";
-
- // Create style element for placeholder styles (instead of directly setting style properties on elements - allows for better flexibility alongside user-defined styles)
- styleElem = document.createElement("style");
- styleElem.type = "text/css";
-
- // Create style rules as text node
- styleRules = document.createTextNode("." + placeholderClassName + " { color:" + placeholderStyleColor + "; }");
-
- // Append style rules to newly created stylesheet
- if (styleElem.styleSheet) {
- styleElem.styleSheet.cssText = styleRules.nodeValue;
- } else {
- styleElem.appendChild(styleRules);
- }
-
- // Prepend new style element to the head (before any existing stylesheets, so user-defined rules take precedence)
- head.insertBefore(styleElem, head.firstChild);
-
- // Set up the placeholders
- for (i = 0, len = inputs.length + textareas.length; i < len; i++) {
- elem = i < inputs.length ? inputs[i] : textareas[i - inputs.length];
-
- // Get the value of the placeholder attribute, if any. IE10 emulating IE7 fails with getAttribute, hence the use of the attributes node
- placeholder = elem.attributes.placeholder;
- if (placeholder) {
-
- // IE returns an empty object instead of undefined if the attribute is not present
- placeholder = placeholder.nodeValue;
-
- // Only apply the polyfill if this element is of a type that supports placeholders, and has a placeholder attribute with a non-empty value
- if (placeholder && Utils.inArray(validTypes, elem.type)) {
- newElement(elem);
- }
- }
- }
-
- // If enabled, the polyfill will repeatedly check for changed/added elements and apply to those as well
- timer = setInterval(function () {
- for (i = 0, len = inputs.length + textareas.length; i < len; i++) {
- elem = i < inputs.length ? inputs[i] : textareas[i - inputs.length];
-
- // Only apply the polyfill if this element is of a type that supports placeholders, and has a placeholder attribute with a non-empty value
- placeholder = elem.attributes.placeholder;
- if (placeholder) {
- placeholder = placeholder.nodeValue;
- if (placeholder && Utils.inArray(validTypes, elem.type)) {
-
- // If the element hasn't had event handlers bound to it then add them
- if (!elem.getAttribute(ATTR_EVENTS_BOUND)) {
- newElement(elem);
- }
-
- // If the placeholder value has changed or not been initialised yet we need to update the display
- if (placeholder !== elem.getAttribute(ATTR_CURRENT_VAL) || (elem.type === "password" && !elem.getAttribute(ATTR_INPUT_TYPE))) {
-
- // Attempt to change the type of password inputs (fails in IE < 9)
- if (elem.type === "password" && !elem.getAttribute(ATTR_INPUT_TYPE) && Utils.changeType(elem, "text")) {
- elem.setAttribute(ATTR_INPUT_TYPE, "password");
- }
-
- // If the placeholder value has changed and the placeholder is currently on display we need to change it
- if (elem.value === elem.getAttribute(ATTR_CURRENT_VAL)) {
- elem.value = placeholder;
- }
-
- // Keep a reference to the current placeholder value in case it changes via another script
- elem.setAttribute(ATTR_CURRENT_VAL, placeholder);
- }
- }
- } else if (elem.getAttribute(ATTR_ACTIVE)) {
- hidePlaceholder(elem);
- elem.removeAttribute(ATTR_CURRENT_VAL);
- }
- }
-
- // If live updates are not enabled cancel the timer
- if (!liveUpdates) {
- clearInterval(timer);
- }
- }, 100);
- }
-
- Utils.addEventListener(global, "beforeunload", function () {
- Placeholders.disable();
- });
-
- // Expose public methods
- Placeholders.disable = Placeholders.nativeSupport ? noop : disablePlaceholders;
- Placeholders.enable = Placeholders.nativeSupport ? noop : enablePlaceholders;
-
-}(this));
diff --git a/core/register_command.php b/core/register_command.php
index a6da3cbd899..89b0cf31ef4 100644
--- a/core/register_command.php
+++ b/core/register_command.php
@@ -83,7 +83,7 @@ if (\OC::$server->getConfig()->getSystemValue('installed', false)) {
$application->add(new OC\Core\Command\Config\System\SetConfig(\OC::$server->getSystemConfig()));
$application->add(new OC\Core\Command\Db\GenerateChangeScript());
- $application->add(new OC\Core\Command\Db\ConvertType(\OC::$server->getConfig(), new \OC\DB\ConnectionFactory()));
+ $application->add(new OC\Core\Command\Db\ConvertType(\OC::$server->getConfig(), new \OC\DB\ConnectionFactory(\OC::$server->getConfig())));
$application->add(new OC\Core\Command\Encryption\Disable(\OC::$server->getConfig()));
$application->add(new OC\Core\Command\Encryption\Enable(\OC::$server->getConfig(), \OC::$server->getEncryptionManager()));
diff --git a/lib/private/DB/AdapterMySQL.php b/lib/private/DB/AdapterMySQL.php
index 3e2fceda8db..aa784bb83dc 100644
--- a/lib/private/DB/AdapterMySQL.php
+++ b/lib/private/DB/AdapterMySQL.php
@@ -27,6 +27,9 @@ namespace OC\DB;
class AdapterMySQL extends Adapter {
+ /** @var string */
+ protected $charset;
+
/**
* @param string $tableName
*/
@@ -39,7 +42,16 @@ class AdapterMySQL extends Adapter {
}
public function fixupStatement($statement) {
- $statement = str_replace(' ILIKE ', ' COLLATE utf8_general_ci LIKE ', $statement);
+ $statement = str_replace(' ILIKE ', ' COLLATE ' . $this->getCharset() . '_general_ci LIKE ', $statement);
return $statement;
}
+
+ protected function getCharset() {
+ if (!$this->charset) {
+ $params = $this->conn->getParams();
+ $this->charset = isset($params['charset']) ? $params['charset'] : 'utf8';
+ }
+
+ return $this->charset;
+ }
}
diff --git a/lib/private/DB/ConnectionFactory.php b/lib/private/DB/ConnectionFactory.php
index b2c356edef7..adb180da0c0 100644
--- a/lib/private/DB/ConnectionFactory.php
+++ b/lib/private/DB/ConnectionFactory.php
@@ -28,6 +28,7 @@ namespace OC\DB;
use Doctrine\DBAL\Event\Listeners\OracleSessionInit;
use Doctrine\DBAL\Event\Listeners\SQLSessionInit;
use Doctrine\DBAL\Event\Listeners\MysqlSessionInit;
+use OCP\IConfig;
/**
* Takes care of creating and configuring Doctrine connections.
@@ -64,6 +65,12 @@ class ConnectionFactory {
),
);
+ public function __construct(IConfig $config) {
+ if($config->getSystemValue('mysql.utf8mb4', false)) {
+ $this->defaultConnectionParams['mysql']['charset'] = 'utf8mb4';
+ }
+ }
+
/**
* @brief Get default connection parameters for a given DBMS.
* @param string $type DBMS type
@@ -99,7 +106,9 @@ class ConnectionFactory {
case 'mysql':
// Send "SET NAMES utf8". Only required on PHP 5.3 below 5.3.6.
// See http://stackoverflow.com/questions/4361459/php-pdo-charset-set-names#4361485
- $eventManager->addEventSubscriber(new MysqlSessionInit);
+ $eventManager->addEventSubscriber(new MysqlSessionInit(
+ $this->defaultConnectionParams['mysql']['charset']
+ ));
$eventManager->addEventSubscriber(
new SQLSessionInit("SET SESSION AUTOCOMMIT=1"));
break;
diff --git a/lib/private/DB/MDB2SchemaReader.php b/lib/private/DB/MDB2SchemaReader.php
index 3f183c9723a..c198bb31e00 100644
--- a/lib/private/DB/MDB2SchemaReader.php
+++ b/lib/private/DB/MDB2SchemaReader.php
@@ -33,6 +33,7 @@ namespace OC\DB;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\SchemaConfig;
+use Doctrine\DBAL\Platforms\MySqlPlatform;
use OCP\IConfig;
class MDB2SchemaReader {
@@ -54,12 +55,16 @@ class MDB2SchemaReader {
/** @var \Doctrine\DBAL\Schema\SchemaConfig $schemaConfig */
protected $schemaConfig;
+ /** @var IConfig */
+ protected $config;
+
/**
* @param \OCP\IConfig $config
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
*/
public function __construct(IConfig $config, AbstractPlatform $platform) {
$this->platform = $platform;
+ $this->config = $config;
$this->DBNAME = $config->getSystemValue('dbname', 'owncloud');
$this->DBTABLEPREFIX = $config->getSystemValue('dbtableprefix', 'oc_');
@@ -118,8 +123,15 @@ class MDB2SchemaReader {
$name = str_replace('*dbprefix*', $this->DBTABLEPREFIX, $name);
$name = $this->platform->quoteIdentifier($name);
$table = $schema->createTable($name);
- $table->addOption('collate', 'utf8_bin');
$table->setSchemaConfig($this->schemaConfig);
+
+ if($this->platform instanceof MySqlPlatform && $this->config->getSystemValue('mysql.utf8mb4', false)) {
+ $table->addOption('charset', 'utf8mb4');
+ $table->addOption('collate', 'utf8mb4_bin');
+ $table->addOption('row_format', 'compressed');
+ } else {
+ $table->addOption('collate', 'utf8_bin');
+ }
break;
case 'create':
case 'overwrite':
diff --git a/lib/private/DB/MDB2SchemaWriter.php b/lib/private/DB/MDB2SchemaWriter.php
index 26e32b036fe..7664b4359ab 100644
--- a/lib/private/DB/MDB2SchemaWriter.php
+++ b/lib/private/DB/MDB2SchemaWriter.php
@@ -43,7 +43,11 @@ class MDB2SchemaWriter {
$xml->addChild('name', $config->getSystemValue('dbname', 'owncloud'));
$xml->addChild('create', 'true');
$xml->addChild('overwrite', 'false');
- $xml->addChild('charset', 'utf8');
+ if($config->getSystemValue('dbtype', 'sqlite') === 'mysql' && $config->getSystemValue('mysql.utf8mb4', false)) {
+ $xml->addChild('charset', 'utf8mb4');
+ } else {
+ $xml->addChild('charset', 'utf8');
+ }
// FIX ME: bloody work around
if ($config->getSystemValue('dbtype', 'sqlite') === 'oci') {
diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php
index 66d8851632f..17f7fd5aa47 100644
--- a/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php
+++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php
@@ -24,18 +24,31 @@
namespace OC\DB\QueryBuilder\ExpressionBuilder;
-use OC\DB\QueryBuilder\QueryFunction;
-use OCP\DB\QueryBuilder\IQueryBuilder;
+use OC\DB\Connection;
+use OCP\IDBConnection;
class MySqlExpressionBuilder extends ExpressionBuilder {
+ /** @var string */
+ protected $charset;
+
+ /**
+ * @param \OCP\IDBConnection|Connection $connection
+ */
+ public function __construct(IDBConnection $connection) {
+ parent::__construct($connection);
+
+ $params = $connection->getParams();
+ $this->charset = isset($params['charset']) ? $params['charset'] : 'utf8';
+ }
+
/**
* @inheritdoc
*/
public function iLike($x, $y, $type = null) {
$x = $this->helper->quoteColumnName($x);
$y = $this->helper->quoteColumnName($y);
- return $this->expressionBuilder->comparison($x, ' COLLATE utf8_general_ci LIKE', $y);
+ return $this->expressionBuilder->comparison($x, ' COLLATE ' . $this->charset . '_general_ci LIKE', $y);
}
}
diff --git a/lib/private/Repair.php b/lib/private/Repair.php
index 2ba118b9c37..7a5ef9fbd9e 100644
--- a/lib/private/Repair.php
+++ b/lib/private/Repair.php
@@ -129,6 +129,7 @@ class Repair implements IOutput{
*/
public static function getRepairSteps() {
return [
+ new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->getDatabaseConnection(), false),
new RepairMimeTypes(\OC::$server->getConfig()),
new RepairLegacyStorages(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()),
new AssetCache(),
@@ -179,7 +180,7 @@ class Repair implements IOutput{
$connection = \OC::$server->getDatabaseConnection();
$steps = [
new InnoDB(),
- new Collation(\OC::$server->getConfig(), $connection),
+ new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), $connection, true),
new SqliteAutoincrement($connection),
new SearchLuceneTables(),
];
diff --git a/lib/private/Repair/Collation.php b/lib/private/Repair/Collation.php
index 74c4ca2e6ac..54de1a719bd 100644
--- a/lib/private/Repair/Collation.php
+++ b/lib/private/Repair/Collation.php
@@ -24,28 +24,38 @@
namespace OC\Repair;
+use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Platforms\MySqlPlatform;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\ILogger;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
class Collation implements IRepairStep {
- /**
- * @var \OCP\IConfig
- */
+ /** @var IConfig */
protected $config;
- /**
- * @var \OC\DB\Connection
- */
+ /** @var ILogger */
+ protected $logger;
+
+ /** @var IDBConnection */
protected $connection;
+ /** @var bool */
+ protected $ignoreFailures;
+
/**
- * @param \OCP\IConfig $config
- * @param \OC\DB\Connection $connection
+ * @param IConfig $config
+ * @param ILogger $logger
+ * @param IDBConnection $connection
+ * @param bool $ignoreFailures
*/
- public function __construct($config, $connection) {
+ public function __construct(IConfig $config, ILogger $logger, IDBConnection $connection, $ignoreFailures) {
$this->connection = $connection;
$this->config = $config;
+ $this->logger = $logger;
+ $this->ignoreFailures = $ignoreFailures;
}
public function getName() {
@@ -61,11 +71,21 @@ class Collation implements IRepairStep {
return;
}
+ $characterSet = $this->config->getSystemValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8';
+
$tables = $this->getAllNonUTF8BinTables($this->connection);
foreach ($tables as $table) {
$output->info("Change collation for $table ...");
- $query = $this->connection->prepare('ALTER TABLE `' . $table . '` CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;');
- $query->execute();
+ $query = $this->connection->prepare('ALTER TABLE `' . $table . '` CONVERT TO CHARACTER SET ' . $characterSet . ' COLLATE ' . $characterSet . '_bin;');
+ try {
+ $query->execute();
+ } catch (DriverException $e) {
+ // Just log this
+ $this->logger->logException($e);
+ if (!$this->ignoreFailures) {
+ throw $e;
+ }
+ }
}
}
@@ -75,11 +95,12 @@ class Collation implements IRepairStep {
*/
protected function getAllNonUTF8BinTables($connection) {
$dbName = $this->config->getSystemValue("dbname");
+ $characterSet = $this->config->getSystemValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8';
$rows = $connection->fetchAll(
"SELECT DISTINCT(TABLE_NAME) AS `table`" .
" FROM INFORMATION_SCHEMA . COLUMNS" .
" WHERE TABLE_SCHEMA = ?" .
- " AND (COLLATION_NAME <> 'utf8_bin' OR CHARACTER_SET_NAME <> 'utf8')" .
+ " AND (COLLATION_NAME <> '" . $characterSet . "_bin' OR CHARACTER_SET_NAME <> '" . $characterSet . "')" .
" AND TABLE_NAME LIKE \"*PREFIX*%\"",
array($dbName)
);
diff --git a/lib/private/Server.php b/lib/private/Server.php
index 291714b23d1..11558118d52 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -407,8 +407,8 @@ class Server extends ServerContainer implements IServerContainer {
return new CredentialsManager($c->getCrypto(), $c->getDatabaseConnection());
});
$this->registerService('DatabaseConnection', function (Server $c) {
- $factory = new \OC\DB\ConnectionFactory();
$systemConfig = $c->getSystemConfig();
+ $factory = new \OC\DB\ConnectionFactory($c->getConfig());
$type = $systemConfig->getValue('dbtype', 'sqlite');
if (!$factory->isValidType($type)) {
throw new \OC\DatabaseException('Invalid database type');
diff --git a/lib/private/Setup/AbstractDatabase.php b/lib/private/Setup/AbstractDatabase.php
index 310f74d4c0c..47c3e5ee1c6 100644
--- a/lib/private/Setup/AbstractDatabase.php
+++ b/lib/private/Setup/AbstractDatabase.php
@@ -134,7 +134,7 @@ abstract class AbstractDatabase {
}
$connectionParams = array_merge($connectionParams, $configOverwrite);
- $cf = new ConnectionFactory();
+ $cf = new ConnectionFactory($this->config);
return $cf->getConnection($this->config->getSystemValue('dbtype', 'sqlite'), $connectionParams);
}
diff --git a/lib/private/Setup/MySQL.php b/lib/private/Setup/MySQL.php
index 4ad6926c2d7..c022616d8b3 100644
--- a/lib/private/Setup/MySQL.php
+++ b/lib/private/Setup/MySQL.php
@@ -58,8 +58,9 @@ class MySQL extends AbstractDatabase {
try{
$name = $this->dbName;
$user = $this->dbUser;
- //we can't use OC_BD functions here because we need to connect as the administrative user.
- $query = "CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET utf8 COLLATE utf8_bin;";
+ //we can't use OC_DB functions here because we need to connect as the administrative user.
+ $characterSet = \OC::$server->getSystemConfig()->getValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8';
+ $query = "CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET $characterSet COLLATE ${characterSet}_bin;";
$connection->executeUpdate($query);
} catch (\Exception $ex) {
$this->logger->error('Database creation failed: {error}', [
diff --git a/lib/private/legacy/template.php b/lib/private/legacy/template.php
index 477fd624a9d..b43b4da839a 100644
--- a/lib/private/legacy/template.php
+++ b/lib/private/legacy/template.php
@@ -143,8 +143,6 @@ class OC_Template extends \OC\Template\Base {
OC_Util::addScript("oc-dialogs", null, true);
OC_Util::addScript("jquery.ocdialog", null, true);
OC_Util::addStyle("jquery.ocdialog");
- OC_Util::addScript("compatibility", null, true);
- OC_Util::addScript("placeholders", null, true);
OC_Util::addScript('files/fileinfo');
OC_Util::addScript('files/client');
diff --git a/tests/data/db_structure.xml b/tests/data/db_structure.xml
index 371da944832..b155114f2ed 100644
--- a/tests/data/db_structure.xml
+++ b/tests/data/db_structure.xml
@@ -293,4 +293,19 @@
</table>
+ <table>
+
+ <name>*dbprefix*text_table</name>
+ <declaration>
+
+ <field>
+ <name>textfield</name>
+ <type>text</type>
+ <notnull>false</notnull>
+ <length>255</length>
+ </field>
+
+ </declaration>
+ </table>
+
</database>
diff --git a/tests/docker/mysqlmb4.config.php b/tests/docker/mysqlmb4.config.php
new file mode 100644
index 00000000000..4e78272fab4
--- /dev/null
+++ b/tests/docker/mysqlmb4.config.php
@@ -0,0 +1,5 @@
+<?php
+
+$CONFIG = array(
+ 'mysql.utf8mb4' => true,
+);
diff --git a/tests/docker/mysqlmb4/mb4.cnf b/tests/docker/mysqlmb4/mb4.cnf
new file mode 100644
index 00000000000..00333eab10d
--- /dev/null
+++ b/tests/docker/mysqlmb4/mb4.cnf
@@ -0,0 +1,5 @@
+
+[mysqld]
+innodb_large_prefix=true
+innodb_file_format=barracuda
+innodb_file_per_table=true
diff --git a/tests/lib/DB/LegacyDBTest.php b/tests/lib/DB/LegacyDBTest.php
index 7aeeb3dd1f9..f3de570c52d 100644
--- a/tests/lib/DB/LegacyDBTest.php
+++ b/tests/lib/DB/LegacyDBTest.php
@@ -46,6 +46,11 @@ class LegacyDBTest extends \Test\TestCase {
*/
private $table5;
+ /**
+ * @var string
+ */
+ private $text_table;
+
protected function setUp() {
parent::setUp();
@@ -63,6 +68,7 @@ class LegacyDBTest extends \Test\TestCase {
$this->table3 = $this->test_prefix.'vcategory';
$this->table4 = $this->test_prefix.'decimal';
$this->table5 = $this->test_prefix.'uniconst';
+ $this->text_table = $this->test_prefix.'text_table';
}
protected function tearDown() {
@@ -390,4 +396,33 @@ class LegacyDBTest extends \Test\TestCase {
$result = $query->execute(array('%ba%'));
$this->assertCount(1, $result->fetchAll());
}
+
+ /**
+ * @dataProvider insertAndSelectDataProvider
+ */
+ public function testInsertAndSelectData($expected, $throwsOnMysqlWithoutUTF8MB4) {
+ $table = "*PREFIX*{$this->text_table}";
+ $config = \OC::$server->getConfig();
+
+ $query = OC_DB::prepare("INSERT INTO `$table` (`textfield`) VALUES (?)");
+ if ($throwsOnMysqlWithoutUTF8MB4 && $config->getSystemValue('dbtype', 'sqlite') === 'mysql' && $config->getSystemValue('mysql.utf8mb4', false) === false) {
+ $this->markTestSkipped('MySQL requires UTF8mb4 to store value: ' . $expected);
+ }
+ $result = $query->execute(array($expected));
+ $this->assertEquals(1, $result);
+
+ $actual = OC_DB::prepare("SELECT `textfield` FROM `$table`")->execute()->fetchOne();
+ $this->assertSame($expected, $actual);
+ }
+
+ public function insertAndSelectDataProvider() {
+ return [
+ ['abcdefghijklmnopqrstuvwxyzABCDEFGHIKLMNOPQRSTUVWXYZ', false],
+ ['0123456789', false],
+ ['äöüÄÖÜß!"§$%&/()=?#\'+*~°^`´', false],
+ ['²³¼½¬{[]}\\', false],
+ ['♡⚗', false],
+ ['💩', true], # :hankey: on github
+ ];
+ }
}
diff --git a/tests/lib/Files/Cache/CacheTest.php b/tests/lib/Files/Cache/CacheTest.php
index 4a2581fbc54..4c4f43d63d5 100644
--- a/tests/lib/Files/Cache/CacheTest.php
+++ b/tests/lib/Files/Cache/CacheTest.php
@@ -113,7 +113,8 @@ class CacheTest extends \Test\TestCase {
public function testFolder($folder) {
if(strpos($folder, 'F09F9890')) {
// 4 byte UTF doesn't work on mysql
- if(\OC::$server->getDatabaseConnection()->getDatabasePlatform() instanceof MySqlPlatform) {
+ $params = \OC::$server->getDatabaseConnection()->getParams();
+ if(\OC::$server->getDatabaseConnection()->getDatabasePlatform() instanceof MySqlPlatform && $params['charset'] !== 'utf8mb4') {
$this->markTestSkipped('MySQL doesn\'t support 4 byte UTF-8');
}
}
diff --git a/tests/lib/Repair/RepairCollationTest.php b/tests/lib/Repair/RepairCollationTest.php
index 2e304a74abc..897f772a794 100644
--- a/tests/lib/Repair/RepairCollationTest.php
+++ b/tests/lib/Repair/RepairCollationTest.php
@@ -1,9 +1,4 @@
<?php
-
-namespace Test\Repair;
-
-use OCP\Migration\IOutput;
-
/**
* Copyright (c) 2014 Thomas Müller <deepdiver@owncloud.com>
* This file is licensed under the Affero General Public License version 3 or
@@ -11,6 +6,11 @@ use OCP\Migration\IOutput;
* See the COPYING-README file.
*/
+namespace Test\Repair;
+
+use OCP\ILogger;
+use OCP\Migration\IOutput;
+
class TestCollationRepair extends \OC\Repair\Collation {
/**
* @param \Doctrine\DBAL\Connection $connection
@@ -50,10 +50,14 @@ class RepairCollationTest extends \Test\TestCase {
*/
private $config;
+ /** @var ILogger */
+ private $logger;
+
protected function setUp() {
parent::setUp();
$this->connection = \OC::$server->getDatabaseConnection();
+ $this->logger = $this->createMock(ILogger::class);
$this->config = \OC::$server->getConfig();
if (!$this->connection->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\MySqlPlatform) {
$this->markTestSkipped("Test only relevant on MySql");
@@ -63,7 +67,7 @@ class RepairCollationTest extends \Test\TestCase {
$this->tableName = $this->getUniqueID($dbPrefix . "_collation_test");
$this->connection->exec("CREATE TABLE $this->tableName(text VARCHAR(16)) COLLATE utf8_unicode_ci");
- $this->repair = new TestCollationRepair($this->config, $this->connection);
+ $this->repair = new TestCollationRepair($this->config, $this->logger, $this->connection, false);
}
protected function tearDown() {