diff --git a/.gitignore b/.gitignore index 25cb1b227f..e61ec6f035 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /data /owncloud /config/config.php +/config/*.config.php /config/mount.php /apps/inc.php diff --git a/apps/files_external/ajax/addRootCertificate.php b/apps/files_external/ajax/addRootCertificate.php index ae349bfcd3..fcd3a617ad 100644 --- a/apps/files_external/ajax/addRootCertificate.php +++ b/apps/files_external/ajax/addRootCertificate.php @@ -4,7 +4,7 @@ OCP\JSON::checkAppEnabled('files_external'); OCP\JSON::callCheck(); if ( ! ($filename = $_FILES['rootcert_import']['name']) ) { - header("Location: settings/personal.php"); + header('Location:' . OCP\Util::linkToRoute( "settings_personal" )); exit; } diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 94dc5fb7ad..b2109e5eac 100755 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -352,9 +352,8 @@ class OC_Mount_Config { $phpFile = OC_User::getHome(OCP\User::getUser()).'/mount.php'; $jsonFile = OC_User::getHome(OCP\User::getUser()).'/mount.json'; } else { - $datadir = \OC_Config::getValue("datadirectory", \OC::$SERVERROOT . "/data"); $phpFile = OC::$SERVERROOT.'/config/mount.php'; - $jsonFile = $datadir . '/mount.json'; + $jsonFile = \OC_Config::getValue("mount_file", \OC::$SERVERROOT . "/data/mount.json"); } if (is_file($jsonFile)) { $mountPoints = json_decode(file_get_contents($jsonFile), true); @@ -380,8 +379,7 @@ class OC_Mount_Config { if ($isPersonal) { $file = OC_User::getHome(OCP\User::getUser()).'/mount.json'; } else { - $datadir = \OC_Config::getValue("datadirectory", \OC::$SERVERROOT . "/data"); - $file = $datadir . '/mount.json'; + $file = \OC_Config::getValue("mount_file", \OC::$SERVERROOT . "/data/mount.json"); } $content = json_encode($data); @file_put_contents($file, $content); diff --git a/apps/user_ldap/lib/helper.php b/apps/user_ldap/lib/helper.php index 9727d847d2..7de7fe8667 100644 --- a/apps/user_ldap/lib/helper.php +++ b/apps/user_ldap/lib/helper.php @@ -118,10 +118,16 @@ class Helper { return false; } + $saveOtherConfigurations = ''; + if(empty($prefix)) { + $saveOtherConfigurations = 'AND `Configkey` NOT LIKE \'s%\''; + } + $query = \OCP\DB::prepare(' DELETE FROM `*PREFIX*appconfig` WHERE `configkey` LIKE ? + '.$saveOtherConfigurations.' AND `appid` = \'user_ldap\' AND `configkey` NOT IN (\'enabled\', \'installed_version\', \'types\', \'bgjUpdateGroupsLastRun\') '); diff --git a/apps/user_ldap/tests/access.php b/apps/user_ldap/tests/access.php new file mode 100644 index 0000000000..9beb2b9733 --- /dev/null +++ b/apps/user_ldap/tests/access.php @@ -0,0 +1,71 @@ +. +* +*/ + +namespace OCA\user_ldap\tests; + +use \OCA\user_ldap\lib\Access; +use \OCA\user_ldap\lib\Connection; +use \OCA\user_ldap\lib\ILDAPWrapper; + +class Test_Access extends \PHPUnit_Framework_TestCase { + private function getConnecterAndLdapMock() { + static $conMethods; + static $accMethods; + + if(is_null($conMethods) || is_null($accMethods)) { + $conMethods = get_class_methods('\OCA\user_ldap\lib\Connection'); + $accMethods = get_class_methods('\OCA\user_ldap\lib\Access'); + } + $lw = $this->getMock('\OCA\user_ldap\lib\ILDAPWrapper'); + $connector = $this->getMock('\OCA\user_ldap\lib\Connection', + $conMethods, + array($lw, null, null)); + + return array($lw, $connector); + } + + public function testEscapeFilterPartValidChars() { + list($lw, $con) = $this->getConnecterAndLdapMock(); + $access = new Access($con, $lw); + + $input = 'okay'; + $this->assertTrue($input === $access->escapeFilterPart($input)); + } + + public function testEscapeFilterPartEscapeWildcard() { + list($lw, $con) = $this->getConnecterAndLdapMock(); + $access = new Access($con, $lw); + + $input = '*'; + $expected = '\\\\*'; + $this->assertTrue($expected === $access->escapeFilterPart($input)); + } + + public function testEscapeFilterPartEscapeWildcard2() { + list($lw, $con) = $this->getConnecterAndLdapMock(); + $access = new Access($con, $lw); + + $input = 'foo*bar'; + $expected = 'foo\\\\*bar'; + $this->assertTrue($expected === $access->escapeFilterPart($input)); + } +} \ No newline at end of file diff --git a/apps/user_ldap/tests/user_ldap.php b/apps/user_ldap/tests/user_ldap.php index 9193a005ae..8c8d85b3c3 100644 --- a/apps/user_ldap/tests/user_ldap.php +++ b/apps/user_ldap/tests/user_ldap.php @@ -83,6 +83,12 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase { * @return void */ private function prepareAccessForCheckPassword(&$access) { + $access->expects($this->once()) + ->method('escapeFilterPart') + ->will($this->returnCallback(function($uid) { + return $uid; + })); + $access->connection->expects($this->any()) ->method('__get') ->will($this->returnCallback(function($name) { @@ -116,17 +122,34 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase { })); } - public function testCheckPassword() { + public function testCheckPasswordUidReturn() { $access = $this->getAccessMock(); + $this->prepareAccessForCheckPassword($access); $backend = new UserLDAP($access); \OC_User::useBackend($backend); $result = $backend->checkPassword('roland', 'dt19'); $this->assertEquals('gunslinger', $result); + } + + public function testCheckPasswordWrongPassword() { + $access = $this->getAccessMock(); + + $this->prepareAccessForCheckPassword($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = $backend->checkPassword('roland', 'wrong'); $this->assertFalse($result); + } + + public function testCheckPasswordWrongUser() { + $access = $this->getAccessMock(); + + $this->prepareAccessForCheckPassword($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = $backend->checkPassword('mallory', 'evil'); $this->assertFalse($result); @@ -140,9 +163,23 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase { $result = \OCP\User::checkPassword('roland', 'dt19'); $this->assertEquals('gunslinger', $result); + } + + public function testCheckPasswordPublicAPIWrongPassword() { + $access = $this->getAccessMock(); + $this->prepareAccessForCheckPassword($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = \OCP\User::checkPassword('roland', 'wrong'); $this->assertFalse($result); + } + + public function testCheckPasswordPublicAPIWrongUser() { + $access = $this->getAccessMock(); + $this->prepareAccessForCheckPassword($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = \OCP\User::checkPassword('mallory', 'evil'); $this->assertFalse($result); @@ -154,6 +191,12 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase { * @return void */ private function prepareAccessForGetUsers(&$access) { + $access->expects($this->once()) + ->method('escapeFilterPart') + ->will($this->returnCallback(function($search) { + return $search; + })); + $access->expects($this->any()) ->method('getFilterPartForUserSearch') ->will($this->returnCallback(function($search) { @@ -191,28 +234,52 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase { ->will($this->returnArgument(0)); } - public function testGetUsers() { + public function testGetUsersNoParam() { $access = $this->getAccessMock(); $this->prepareAccessForGetUsers($access); $backend = new UserLDAP($access); $result = $backend->getUsers(); $this->assertEquals(3, count($result)); + } + + public function testGetUsersLimitOffset() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); $result = $backend->getUsers('', 1, 2); $this->assertEquals(1, count($result)); + } + + public function testGetUsersLimitOffset2() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); $result = $backend->getUsers('', 2, 1); $this->assertEquals(2, count($result)); + } + + public function testGetUsersSearchWithResult() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); $result = $backend->getUsers('yo'); $this->assertEquals(2, count($result)); + } + + public function testGetUsersSearchEmptyResult() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); $result = $backend->getUsers('nix'); $this->assertEquals(0, count($result)); } - public function testGetUsersViaAPI() { + public function testGetUsersViaAPINoParam() { $access = $this->getAccessMock(); $this->prepareAccessForGetUsers($access); $backend = new UserLDAP($access); @@ -220,15 +287,43 @@ class Test_User_Ldap_Direct extends \PHPUnit_Framework_TestCase { $result = \OCP\User::getUsers(); $this->assertEquals(3, count($result)); + } + + public function testGetUsersViaAPILimitOffset() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = \OCP\User::getUsers('', 1, 2); $this->assertEquals(1, count($result)); + } + + public function testGetUsersViaAPILimitOffset2() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = \OCP\User::getUsers('', 2, 1); $this->assertEquals(2, count($result)); + } + + public function testGetUsersViaAPISearchWithResult() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = \OCP\User::getUsers('yo'); $this->assertEquals(2, count($result)); + } + + public function testGetUsersViaAPISearchEmptyResult() { + $access = $this->getAccessMock(); + $this->prepareAccessForGetUsers($access); + $backend = new UserLDAP($access); + \OC_User::useBackend($backend); $result = \OCP\User::getUsers('nix'); $this->assertEquals(0, count($result)); diff --git a/config/config.sample.php b/config/config.sample.php index 0cd321d095..9f47ee3294 100755 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -263,4 +263,7 @@ $CONFIG = array( /* whether usage of the instance should be restricted to admin users only */ 'singleuser' => false, + + /* where mount.json file should be stored, defaults to data/mount.json */ + 'mount_file' => '', ); diff --git a/core/css/fixes.css b/core/css/fixes.css index 4ee854adde..a33afd5cf7 100644 --- a/core/css/fixes.css +++ b/core/css/fixes.css @@ -54,11 +54,6 @@ background-color: #1B314D; } -/* in IE9 the nav bar on the left side is too narrow and leave a white area - original width is 80px */ -.ie9 #navigation { - width: 100px; -} - /* IE8 isn't able to display transparent background. So it is specified using a gradient */ .ie8 #nojavascript { filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#4c320000', endColorstr='#4c320000'); /* IE */ diff --git a/core/css/mobile.css b/core/css/mobile.css new file mode 100644 index 0000000000..a63aa902d3 --- /dev/null +++ b/core/css/mobile.css @@ -0,0 +1,22 @@ +@media only screen and (max-width: 600px) { + +/* compress search box on mobile, expand when focused */ +.searchbox input[type="search"] { + width: 15%; + -webkit-transition: width 100ms; + -moz-transition: width 100ms; + -o-transition: width 100ms; + transition: width 100ms; +} +.searchbox input[type="search"]:focus, +.searchbox input[type="search"]:active { + width: 155px; +} + +/* do not show display name on mobile when profile picture is present */ +#header .avatardiv.avatardiv-shown + #expandDisplayName { + display: none; +} + + +} diff --git a/core/css/multiselect.css b/core/css/multiselect.css index 60f2f47e69..8d949e7cdb 100644 --- a/core/css/multiselect.css +++ b/core/css/multiselect.css @@ -48,7 +48,7 @@ ul.multiselectoptions > li input[type='checkbox']:checked+label { font-weight: bold; } -div.multiselect { +div.multiselect, select.multiselect { display: inline-block; max-width: 400px; min-width: 150px; @@ -58,6 +58,12 @@ div.multiselect { vertical-align: bottom; } +/* To make a select look like a multiselect until it's initialized */ +select.multiselect { + height: 30px; + min-width: 113px; +} + div.multiselect.active { background-color: #fff; position: relative; diff --git a/core/css/styles.css b/core/css/styles.css index bee44785f1..341a507ce3 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -37,11 +37,12 @@ body { background:#fefefe; font:normal .8em/1.6em "Helvetica Neue",Helvetica,Ari .header-right { float:right; vertical-align:middle; padding:0.5em; } .header-right > * { vertical-align:middle; } +/* Profile picture in header */ #header .avatardiv { float: left; display: inline-block; + margin-right: 5px; } - #header .avatardiv img { opacity: 1; } @@ -74,6 +75,19 @@ body { background:#fefefe; font:normal .8em/1.6em "Helvetica Neue",Helvetica,Ari color: #aaa; } +#header .logo { + background-image: url(../img/logo.svg); + width: 250px; + height: 118px; + margin: 0 auto; +} + +#header .logo-wide { + background-image: url(../img/logo-wide.svg); + width: 147px; + height: 32px; +} + /* INPUTS */ input[type="text"], input[type="password"], @@ -205,17 +219,19 @@ textarea:disabled { color: #bbb; } - +/* Searchbox */ .searchbox input[type="search"] { + position: relative; font-size: 1.2em; - padding: .2em .5em .2em 1.5em; + padding-left: 1.5em; background: #fff url('../img/actions/search.svg') no-repeat .5em center; border: 0; - border-radius: 1em; + border-radius: 2em; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=70)"; filter:alpha(opacity=70); opacity: .7; - margin-top: 10px; + margin-top: 6px; float: right; } + input[type="submit"].enabled { background: #66f866; border: 1px solid #5e5; @@ -706,12 +722,11 @@ label.infield { cursor:text !important; top:1.05em; left:.85em; } /* USER MENU */ #settings { float: right; - margin-top: 7px; - margin-left: 10px; color: #bbb; } #expand { - padding: 15px 15px 15px 5px; + display: block; + padding: 7px 12px 6px 7px; cursor: pointer; font-weight: bold; } @@ -933,3 +948,20 @@ div.crumb:active { opacity:.7; } +.appear { + opacity: 1; + transition: opacity 500ms ease 0s; + -moz-transition: opacity 500ms ease 0s; + -ms-transition: opacity 500ms ease 0s; + -o-transition: opacity 500ms ease 0s; + -webkit-transition: opacity 500ms ease 0s; +} +.appear.transparent { + opacity: 0; +} + +/* for IE10 */ +@-ms-viewport { + width: device-width; +} + diff --git a/core/img/filetypes/text-x-javascript.png b/core/img/filetypes/application-javascript.png similarity index 100% rename from core/img/filetypes/text-x-javascript.png rename to core/img/filetypes/application-javascript.png diff --git a/core/img/filetypes/text-x-javascript.svg b/core/img/filetypes/application-javascript.svg similarity index 100% rename from core/img/filetypes/text-x-javascript.svg rename to core/img/filetypes/application-javascript.svg diff --git a/core/img/filetypes/flash.png b/core/img/filetypes/application-x-shockwave-flash.png similarity index 100% rename from core/img/filetypes/flash.png rename to core/img/filetypes/application-x-shockwave-flash.png diff --git a/core/img/filetypes/flash.svg b/core/img/filetypes/application-x-shockwave-flash.svg similarity index 100% rename from core/img/filetypes/flash.svg rename to core/img/filetypes/application-x-shockwave-flash.svg diff --git a/core/img/filetypes/calendar.png b/core/img/filetypes/text-calendar.png similarity index 100% rename from core/img/filetypes/calendar.png rename to core/img/filetypes/text-calendar.png diff --git a/core/img/filetypes/calendar.svg b/core/img/filetypes/text-calendar.svg similarity index 100% rename from core/img/filetypes/calendar.svg rename to core/img/filetypes/text-calendar.svg diff --git a/core/js/avatar.js b/core/js/avatar.js index c54c406876..67d6b9b7b9 100644 --- a/core/js/avatar.js +++ b/core/js/avatar.js @@ -1,6 +1,13 @@ $(document).ready(function(){ if (OC.currentUser) { - $('#header .avatardiv').avatar(OC.currentUser, 32, undefined, true); + var callback = function() { + // do not show display name on mobile when profile picture is present + if($('#header .avatardiv').children().length > 0) { + $('#header .avatardiv').addClass('avatardiv-shown'); + } + }; + + $('#header .avatardiv').avatar(OC.currentUser, 32, undefined, true, callback); // Personal settings $('#avatar .avatardiv').avatar(OC.currentUser, 128); } diff --git a/core/js/config.php b/core/js/config.php index e79b454130..947577eb98 100644 --- a/core/js/config.php +++ b/core/js/config.php @@ -27,6 +27,7 @@ foreach(OC_App::getEnabledApps() as $app) { $array = array( "oc_debug" => (defined('DEBUG') && DEBUG) ? 'true' : 'false', + "oc_isadmin" => OC_User::isAdminUser(OC_User::getUser()) ? 'true' : 'false', "oc_webroot" => "\"".OC::$WEBROOT."\"", "oc_appswebroots" => str_replace('\\/', '/', json_encode($apps_paths)), // Ugly unescape slashes waiting for better solution "datepickerFormatDate" => json_encode($l->l('jsdate', 'jsdate')), diff --git a/core/js/jquery.avatar.js b/core/js/jquery.avatar.js index 6012eccfad..02a40c088b 100644 --- a/core/js/jquery.avatar.js +++ b/core/js/jquery.avatar.js @@ -39,10 +39,15 @@ * This will behave like the first example, but it will hide the avatardiv, if * it will display the default placeholder. undefined is the ie8fix from * example 4 and can be either true, or false/undefined, to be ignored. + * + * 6. $('.avatardiv').avatar('jdoe', 128, undefined, true, callback); + * This will behave like the above example, but it will call the function + * defined in callback after the avatar is placed into the DOM. + * */ (function ($) { - $.fn.avatar = function(user, size, ie8fix, hidedefault) { + $.fn.avatar = function(user, size, ie8fix, hidedefault, callback) { if (typeof(size) === 'undefined') { if (this.height() > 0) { size = this.height(); @@ -91,6 +96,9 @@ $div.html(''); } } + if(typeof callback === 'function') { + callback(); + } }); }); }; diff --git a/core/js/js.js b/core/js/js.js index d4d2583f1e..ec890be454 100644 --- a/core/js/js.js +++ b/core/js/js.js @@ -860,6 +860,7 @@ function initCore() { // checkShowCredentials(); // $('input#user, input#password').keyup(checkShowCredentials); + // user menu $('#settings #expand').keydown(function(event) { if (event.which === 13 || event.which === 32) { $('#expand').click() @@ -872,7 +873,8 @@ function initCore() { $('#settings #expanddiv').click(function(event){ event.stopPropagation(); }); - $(document).click(function(){//hide the settings menu when clicking outside it + //hide the user menu when clicking outside it + $(document).click(function(){ $('#settings #expanddiv').slideUp(200); }); @@ -987,6 +989,17 @@ OC.set=function(name, value) { context[tail]=value; }; +// fix device width on windows phone +(function() { + if ("-ms-user-select" in document.documentElement.style && navigator.userAgent.match(/IEMobile\/10\.0/)) { + var msViewportStyle = document.createElement("style"); + msViewportStyle.appendChild( + document.createTextNode("@-ms-viewport{width:auto!important}") + ); + document.getElementsByTagName("head")[0].appendChild(msViewportStyle); + } +})(); + /** * select a range in an input field * @link http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area diff --git a/core/templates/layout.guest.php b/core/templates/layout.guest.php index 6a96b17b10..91157b923a 100644 --- a/core/templates/layout.guest.php +++ b/core/templates/layout.guest.php @@ -36,7 +36,7 @@
diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index bc1c700402..3d89750348 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -15,7 +15,7 @@ - + @@ -45,17 +45,18 @@