Merge branch 'master' into filesystem

This commit is contained in:
Robin Appelman 2012-10-26 22:32:44 +02:00
commit 4b616764e8
9 changed files with 253 additions and 199 deletions

View File

@ -26,6 +26,9 @@ namespace OCA\user_ldap\lib;
abstract class Access {
protected $connection;
//never ever check this var directly, always use getPagedSearchResultState
protected $pagedSearchedSuccessful;
public function setConnector(Connection &$connection) {
$this->connection = $connection;
}
@ -441,12 +444,12 @@ abstract class Access {
return true;
}
public function fetchListOfUsers($filter, $attr) {
return $this->fetchList($this->searchUsers($filter, $attr), (count($attr) > 1));
public function fetchListOfUsers($filter, $attr, $limit = null, $offset = null) {
return $this->fetchList($this->searchUsers($filter, $attr, $limit, $offset), (count($attr) > 1));
}
public function fetchListOfGroups($filter, $attr) {
return $this->fetchList($this->searchGroups($filter, $attr), (count($attr) > 1));
public function fetchListOfGroups($filter, $attr, $limit = null, $offset = null) {
return $this->fetchList($this->searchGroups($filter, $attr, $limit, $offset), (count($attr) > 1));
}
private function fetchList($list, $manyAttributes) {
@ -470,8 +473,8 @@ abstract class Access {
*
* Executes an LDAP search
*/
public function searchUsers($filter, $attr = null) {
return $this->search($filter, $this->connection->ldapBaseUsers, $attr);
public function searchUsers($filter, $attr = null, $limit = null, $offset = null) {
return $this->search($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
}
/**
@ -482,8 +485,8 @@ abstract class Access {
*
* Executes an LDAP search
*/
public function searchGroups($filter, $attr = null) {
return $this->search($filter, $this->connection->ldapBaseGroups, $attr);
public function searchGroups($filter, $attr = null, $limit = null, $offset = null) {
return $this->search($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
}
/**
@ -495,29 +498,73 @@ abstract class Access {
*
* Executes an LDAP search
*/
private function search($filter, $base, $attr = null) {
private function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
if(!is_null($attr) && !is_array($attr)) {
$attr = array(mb_strtolower($attr, 'UTF-8'));
}
// See if we have a resource
// See if we have a resource, in case not cancel with message
$link_resource = $this->connection->getConnectionResource();
if(is_resource($link_resource)) {
$sr = ldap_search($link_resource, $base, $filter, $attr);
$findings = ldap_get_entries($link_resource, $sr );
// if we're here, probably no connection resource is returned.
// to make ownCloud behave nicely, we simply give back an empty array.
if(is_null($findings)) {
return array();
}
} else {
if(!is_resource($link_resource)) {
// Seems like we didn't find any resource.
// Return an empty array just like before.
\OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', \OCP\Util::DEBUG);
return array();
}
//TODO: lines 516:540 into a function of its own. $pagedSearchOK as return
//check wether paged query should be attempted
$pagedSearchOK = false;
if($this->connection->hasPagedResultSupport && !is_null($limit)) {
$offset = intval($offset); //can be null
//get the cookie from the search for the previous search, required by LDAP
$cookie = $this->getPagedResultCookie($filter, $limit, $offset);
if(empty($cookie) && ($offset > 0)) {
//no cookie known, although the offset is not 0. Maybe cache run out. We need to start all over *sigh* (btw, Dear Reader, did you need LDAP paged searching was designed by MSFT?)
$reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit;
//a bit recursive, $offset of 0 is the exit
$this->search($filter, $base, $attr, $limit, $reOffset, true);
$cookie = $this->getPagedResultCookie($filter, $limit, $offset);
//still no cookie? obviously, the server does not like us. Let's skip paging efforts.
//TODO: remember this, probably does not change in the next request...
if(empty($cookie)) {
$cookie = null;
}
}
if(!is_null($cookie)) {
$pagedSearchOK = ldap_control_paged_result($link_resource, $limit, false, $cookie);
\OCP\Util::writeLog('user_ldap', 'Ready for a paged search', \OCP\Util::DEBUG);
} else {
\OCP\Util::writeLog('user_ldap', 'No paged search for us, Cpt., Limit '.$limit.' Offset '.$offset, \OCP\Util::DEBUG);
}
}
$sr = ldap_search($link_resource, $base, $filter, $attr);
$findings = ldap_get_entries($link_resource, $sr );
if($pagedSearchOK) {
\OCP\Util::writeLog('user_ldap', 'Paged search successful', \OCP\Util::INFO);
ldap_control_paged_result_response($link_resource, $sr, $cookie);
\OCP\Util::writeLog('user_ldap', 'Set paged search cookie '.$cookie, \OCP\Util::INFO);
$this->setPagedResultCookie($filter, $limit, $offset, $cookie);
//browsing through prior pages to get the cookie for the new one
if($skipHandling) {
return;
}
//if count is bigger, then the server does not support paged search. Instead, he did a normal search. We set a flag here, so the callee knows how to deal with it.
//TODO: Not used, just make a count on the returned values in the callee
if($findings['count'] <= $limit) {
$this->pagedSearchedSuccessful = true;
}
} else {
\OCP\Util::writeLog('user_ldap', 'Paged search failed :(', \OCP\Util::INFO);
}
// if we're here, probably no connection resource is returned.
// to make ownCloud behave nicely, we simply give back an empty array.
if(is_null($findings)) {
return array();
}
if(!is_null($attr)) {
$selection = array();
$multiarray = false;
@ -557,6 +604,7 @@ abstract class Access {
}
}
}
// die(var_dump($selection));
return $selection;
}
return $findings;
@ -680,4 +728,51 @@ abstract class Access {
}
return $uuid;
}
/**
* @brief get a cookie for the next LDAP paged search
* @param $filter the search filter to identify the correct search
* @param $limit the limit (or 'pageSize'), to identify the correct search well
* @param $offset the offset for the new search to identify the correct search really good
* @returns string containing the key or empty if none is cached
*/
private function getPagedResultCookie($filter, $limit, $offset) {
if($offset == 0) {
return '';
}
$offset -= $limit;
//we work with cache here
$cachekey = 'lc' . dechex(crc32($filter)) . '-' . $limit . '-' . $offset;
$cookie = $this->connection->getFromCache($cachekey);
if(is_null($cookie)) {
$cookie = '';
}
return $cookie;
}
/**
* @brief set a cookie for LDAP paged search run
* @param $filter the search filter to identify the correct search
* @param $limit the limit (or 'pageSize'), to identify the correct search well
* @param $offset the offset for the run search to identify the correct search really good
* @param $cookie string containing the cookie returned by ldap_control_paged_result_response
* @return void
*/
private function setPagedResultCookie($filter, $limit, $offset) {
if(!empty($cookie)) {
$cachekey = 'lc' . dechex(crc32($filter)) . '-' . $limit . '-' . $offset;
$cookie = $this->connection->writeToCache($cachekey, $cookie);
}
}
/**
* @brief check wether the most recent paged search was successful. It flushed the state var. Use it always after a possible paged search.
* @return true on success, null or false otherwise
*/
public function getPagedSearchResultState() {
$result = $this->pagedSearchedSuccessful;
$this->pagedSearchedSuccessful = null;
return $result;
}
}

View File

@ -56,15 +56,20 @@ class Connection {
'ldapUuidAttribute' => null,
'ldapOverrideUuidAttribute' => null,
'homeFolderNamingRule' => null,
'hasPagedResultSupport' => false,
);
public function __construct($configID = 'user_ldap') {
$this->configID = $configID;
$this->cache = \OC_Cache::getGlobalCache();
$this->config['hasPagedResultSupport'] = (function_exists('ldap_control_paged_result') && function_exists('ldap_control_paged_result_response'));
\OCP\Util::writeLog('user_ldap', 'PHP supports paged results? '.print_r($this->config['hasPagedResultSupport'], true), \OCP\Util::INFO);
}
public function __destruct() {
@ldap_unbind($this->ldapConnectionRes);
if(is_resource($this->ldapConnectionRes)) {
@ldap_unbind($this->ldapConnectionRes);
};
}
public function __get($name) {

View File

@ -104,24 +104,38 @@ class USER_LDAP extends lib\Access implements \OCP\UserInterface {
* Get a list of all users.
*/
public function getUsers($search = '', $limit = 10, $offset = 0) {
$ldap_users = $this->connection->getFromCache('getUsers');
if(is_null($ldap_users)) {
$ldap_users = $this->fetchListOfUsers($this->connection->ldapUserFilter, array($this->connection->ldapUserDisplayName, 'dn'));
$ldap_users = $this->ownCloudUserNames($ldap_users);
$this->connection->writeToCache('getUsers', $ldap_users);
}
$this->userSearch = $search;
if(!empty($this->userSearch)) {
$ldap_users = array_filter($ldap_users, array($this, 'userMatchesFilter'));
}
if($limit == -1) {
$limit = null;
}
return array_slice($ldap_users, $offset, $limit);
}
$cachekey = 'getUsers-'.$search.'-'.$limit.'-'.$offset;
public function userMatchesFilter($user) {
return (strripos($user, $this->userSearch) !== false);
//check if users are cached, if so return
$ldap_users = $this->connection->getFromCache($cachekey);
if(!is_null($ldap_users)) {
return $ldap_users;
}
//prepare search filter
$search = empty($search) ? '*' : '*'.$search.'*';
$filter = $this->combineFilterWithAnd(array(
$this->connection->ldapUserFilter,
$this->connection->ldapGroupDisplayName.'='.$search
));
\OCP\Util::writeLog('user_ldap', 'getUsers: Get users filter '.$filter, \OCP\Util::DEBUG);
//do the search and translate results to owncloud names
$ldap_users = $this->fetchListOfUsers($filter, array($this->connection->ldapUserDisplayName, 'dn'), $limit, $offset);
$ldap_users = $this->ownCloudUserNames($ldap_users);
if(!$this->getPagedSearchResultState()) {
\OCP\Util::writeLog('user_ldap', 'getUsers: We got old-style results', \OCP\Util::DEBUG);
//if not supported, a 'normal' search has run automatically, we just need to get our slice of the cake. And we cache the general search, too
$this->connection->writeToCache('getUsers-'.$search, $ldap_users);
$ldap_users = array_slice($ldap_users, $offset, $limit);
} else {
//debug message only
\OCP\Util::writeLog('user_ldap', 'getUsers: We got paged results', \OCP\Util::DEBUG);
}
$this->connection->writeToCache($cachekey, $ldap_users);
return $ldap_users;
}
/**

View File

@ -0,0 +1,22 @@
<?php
$RUNTIME_NOAPPS = true;
require_once '../../lib/base.php';
OC_Util::checkAdminUser();
OCP\JSON::callCheck();
$app = $_GET['app'];
//load the one app and see what it adds to the navigation
OC_App::loadApp($app);
$navigation = OC_App::getNavigation();
$navIds = array();
foreach ($navigation as $nav) {
$navIds[] = $nav['id'];
}
OCP\JSON::success(array('nav_ids' => array_values($navIds), 'nav_entries' => $navigation));

View File

@ -1,140 +0,0 @@
/*
* In-Field Label jQuery Plugin
* http://fuelyourcoding.com/scripts/infield.html
*
* Copyright (c) 2009 Doug Neiner
* Dual licensed under the MIT and GPL licenses.
* Uses the same license as jQuery, see:
* http://docs.jquery.com/License
*
* @version 0.1
*/
(function($){
$.InFieldLabels = function(label,field, options){
// To avoid scope issues, use 'base' instead of 'this'
// to reference this class from internal events and functions.
var base = this;
// Access to jQuery and DOM versions of each element
base.$label = $(label);
base.label = label;
base.$field = $(field);
base.field = field;
base.$label.data("InFieldLabels", base);
base.showing = true;
base.init = function(){
// Merge supplied options with default options
base.options = $.extend({},$.InFieldLabels.defaultOptions, options);
// Check if the field is already filled in
if(base.$field.val() != ""){
base.$label.hide();
base.showing = false;
};
base.$field.focus(function(){
base.fadeOnFocus();
}).blur(function(){
base.checkForEmpty(true);
}).bind('keydown.infieldlabel',function(e){
// Use of a namespace (.infieldlabel) allows us to
// unbind just this method later
base.hideOnChange(e);
}).change(function(e){
base.checkForEmpty();
}).bind('onPropertyChange', function(){
base.checkForEmpty();
});
};
// If the label is currently showing
// then fade it down to the amount
// specified in the settings
base.fadeOnFocus = function(){
if(base.showing){
base.setOpacity(base.options.fadeOpacity);
};
};
base.setOpacity = function(opacity){
base.$label.stop().animate({ opacity: opacity }, base.options.fadeDuration);
base.showing = (opacity > 0.0);
};
// Checks for empty as a fail safe
// set blur to true when passing from
// the blur event
base.checkForEmpty = function(blur){
if(base.$field.val() == ""){
base.prepForShow();
base.setOpacity( blur ? 1.0 : base.options.fadeOpacity );
} else {
base.setOpacity(0.0);
};
};
base.prepForShow = function(e){
if(!base.showing) {
// Prepare for a animate in...
base.$label.css({opacity: 0.0}).show();
// Reattach the keydown event
base.$field.bind('keydown.infieldlabel',function(e){
base.hideOnChange(e);
});
};
};
base.hideOnChange = function(e){
if(
(e.keyCode == 16) || // Skip Shift
(e.keyCode == 9) // Skip Tab
) return;
if(base.showing){
base.$label.hide();
base.showing = false;
};
// Remove keydown event to save on CPU processing
base.$field.unbind('keydown.infieldlabel');
};
// Run the initialization method
base.init();
};
$.InFieldLabels.defaultOptions = {
fadeOpacity: 0.5, // Once a field has focus, how transparent should the label be
fadeDuration: 300 // How long should it take to animate from 1.0 opacity to the fadeOpacity
};
$.fn.inFieldLabels = function(options){
return this.each(function(){
// Find input or textarea based on for= attribute
// The for attribute on the label must contain the ID
// of the input or textarea element
var for_attr = $(this).attr('for');
if( !for_attr ) return; // Nothing to attach, since the for field wasn't used
// Find the referenced input or textarea element
var $field = $(
"input#" + for_attr + "[type='text']," +
"input#" + for_attr + "[type='password']," +
"textarea#" + for_attr
);
if( $field.length == 0) return; // Again, nothing to attach
// Only create object for input[text], input[password], or textarea
(new $.InFieldLabels(this, $field[0], options));
});
};
})(jQuery);

View File

@ -1,12 +1,12 @@
/*
* In-Field Label jQuery Plugin
* http://fuelyourcoding.com/scripts/infield.html
*
* Copyright (c) 2009 Doug Neiner
* Dual licensed under the MIT and GPL licenses.
* Uses the same license as jQuery, see:
* http://docs.jquery.com/License
*
* @version 0.1
*/
(function($){$.InFieldLabels=function(b,c,d){var f=this;f.$label=$(b);f.label=b;f.$field=$(c);f.field=c;f.$label.data("InFieldLabels",f);f.showing=true;f.init=function(){f.options=$.extend({},$.InFieldLabels.defaultOptions,d);if(f.$field.val()!=""){f.$label.hide();f.showing=false};f.$field.focus(function(){f.fadeOnFocus()}).blur(function(){f.checkForEmpty(true)}).bind('keydown.infieldlabel',function(e){f.hideOnChange(e)}).change(function(e){f.checkForEmpty()}).bind('onPropertyChange',function(){f.checkForEmpty()})};f.fadeOnFocus=function(){if(f.showing){f.setOpacity(f.options.fadeOpacity)}};f.setOpacity=function(a){f.$label.stop().animate({opacity:a},f.options.fadeDuration);f.showing=(a>0.0)};f.checkForEmpty=function(a){if(f.$field.val()==""){f.prepForShow();f.setOpacity(a?1.0:f.options.fadeOpacity)}else{f.setOpacity(0.0)}};f.prepForShow=function(e){if(!f.showing){f.$label.css({opacity:0.0}).show();f.$field.bind('keydown.infieldlabel',function(e){f.hideOnChange(e)})}};f.hideOnChange=function(e){if((e.keyCode==16)||(e.keyCode==9))return;if(f.showing){f.$label.hide();f.showing=false};f.$field.unbind('keydown.infieldlabel')};f.init()};$.InFieldLabels.defaultOptions={fadeOpacity:0.5,fadeDuration:300};$.fn.inFieldLabels=function(c){return this.each(function(){var a=$(this).attr('for');if(!a)return;var b=$("input#"+a+"[type='text'],"+"input#"+a+"[type='password'],"+"textarea#"+a);if(b.length==0)return;(new $.InFieldLabels(this,b[0],c))})}})(jQuery);
In-Field Label jQuery Plugin
http://fuelyourcoding.com/scripts/infield.html
Copyright (c) 2009-2010 Doug Neiner
Dual licensed under the MIT and GPL licenses.
Uses the same license as jQuery, see:
http://docs.jquery.com/License
@version 0.1.5
*/
(function($){$.InFieldLabels=function(label,field,options){var base=this;base.$label=$(label);base.label=label;base.$field=$(field);base.field=field;base.$label.data("InFieldLabels",base);base.showing=true;base.init=function(){base.options=$.extend({},$.InFieldLabels.defaultOptions,options);setTimeout(function(){if(base.$field.val()!==""){base.$label.hide();base.showing=false}},200);base.$field.focus(function(){base.fadeOnFocus()}).blur(function(){base.checkForEmpty(true)}).bind('keydown.infieldlabel',function(e){base.hideOnChange(e)}).bind('paste',function(e){base.setOpacity(0.0)}).change(function(e){base.checkForEmpty()}).bind('onPropertyChange',function(){base.checkForEmpty()}).bind('keyup.infieldlabel',function(){base.checkForEmpty()})};base.fadeOnFocus=function(){if(base.showing){base.setOpacity(base.options.fadeOpacity)}};base.setOpacity=function(opacity){base.$label.stop().animate({opacity:opacity},base.options.fadeDuration);base.showing=(opacity>0.0)};base.checkForEmpty=function(blur){if(base.$field.val()===""){base.prepForShow();base.setOpacity(blur?1.0:base.options.fadeOpacity)}else{base.setOpacity(0.0)}};base.prepForShow=function(e){if(!base.showing){base.$label.css({opacity:0.0}).show();base.$field.bind('keydown.infieldlabel',function(e){base.hideOnChange(e)})}};base.hideOnChange=function(e){if((e.keyCode===16)||(e.keyCode===9)){return}if(base.showing){base.$label.hide();base.showing=false}base.$field.unbind('keydown.infieldlabel')};base.init()};$.InFieldLabels.defaultOptions={fadeOpacity:0.5,fadeDuration:300};$.fn.inFieldLabels=function(options){return this.each(function(){var for_attr=$(this).attr('for'),$field;if(!for_attr){return}$field=$("input#"+for_attr+"[type='text'],"+"input#"+for_attr+"[type='search'],"+"input#"+for_attr+"[type='tel'],"+"input#"+for_attr+"[type='url'],"+"input#"+for_attr+"[type='email'],"+"input#"+for_attr+"[type='password'],"+"textarea#"+for_attr);if($field.length===0){return}(new $.InFieldLabels(this,$field[0],options))})}}(jQuery));

View File

@ -373,20 +373,15 @@ class OC_Helper {
}
if (!$isWrapped and $mimeType=='application/octet-stream' && OC_Helper::canExecute("file")) {
// it looks like we have a 'file' command,
// lets see it it does have mime support
// lets see if it does have mime support
$path=escapeshellarg($path);
$fp = popen("file -i -b $path 2>/dev/null", "r");
$reply = fgets($fp);
pclose($fp);
//trim the character set from the end of the response
$mimeType=substr($reply,0, strrpos($reply,' '));
$mimeType=substr($mimeType,0, strrpos($mimeType,"\n"));
//trim ;
if (strpos($mimeType, ';') !== false) {
$mimeType = strstr($mimeType, ';', true);
}
// we have smth like 'text/x-c++; charset=us-ascii\n'
// and need to eliminate everything starting with semicolon including trailing LF
$mimeType = preg_replace('/;.*/ms', '', trim($reply));
}
return $mimeType;

View File

@ -28,6 +28,9 @@ namespace OCP;
/**
* This class provides the ability for apps to share their content between users.
* Apps must create a backend class that implements OCP\Share_Backend and register it with this class.
*
* It provides the following hooks:
* - post_shared
*/
class Share {
@ -937,7 +940,20 @@ class Share {
// Insert an extra row for the group share if the item or file target is unique for this user
if ($itemTarget != $groupItemTarget || (isset($fileSource) && $fileTarget != $groupFileTarget)) {
$query->execute(array($itemType, $itemSource, $itemTarget, $parent, self::$shareTypeGroupUserUnique, $uid, $uidOwner, $permissions, time(), $fileSource, $fileTarget));
\OC_DB::insertid('*PREFIX*share');
$id = \OC_DB::insertid('*PREFIX*share');
\OC_Hook::emit('OCP\Share', 'post_shared', array(
'itemType' => $itemType,
'itemSource' => $itemSource,
'itemTarget' => $itemTarget,
'parent' => $parent,
'shareType' => self::$shareTypeGroupUserUnique,
'shareWith' => $uid,
'uidOwner' => $uidOwner,
'permissions' => $permissions,
'fileSource' => $fileSource,
'fileTarget' => $fileTarget,
'id' => $id
));
}
}
if ($parentFolder === true) {
@ -963,6 +979,19 @@ class Share {
}
$query->execute(array($itemType, $itemSource, $itemTarget, $parent, $shareType, $shareWith, $uidOwner, $permissions, time(), $fileSource, $fileTarget));
$id = \OC_DB::insertid('*PREFIX*share');
\OC_Hook::emit('OCP\Share', 'post_shared', array(
'itemType' => $itemType,
'itemSource' => $itemSource,
'itemTarget' => $itemTarget,
'parent' => $parent,
'shareType' => $shareType,
'shareWith' => $shareWith,
'uidOwner' => $uidOwner,
'permissions' => $permissions,
'fileSource' => $fileSource,
'fileTarget' => $fileTarget,
'id' => $id
));
if ($parentFolder === true) {
$parentFolders['id'] = $id;
// Return parent folder to preserve file target paths for potential children

View File

@ -51,6 +51,7 @@ OC.Settings.Apps = OC.Settings.Apps || {
}
else {
element.data('active',false);
OC.Settings.Apps.removeNavigation(appid);
element.val(t('settings','Enable'));
}
},'json');
@ -61,6 +62,7 @@ OC.Settings.Apps = OC.Settings.Apps || {
OC.dialogs.alert('Error while enabling app','Error');
}
else {
OC.Settings.Apps.addNavigation(appid);
element.data('active',true);
element.val(t('settings','Disable'));
}
@ -87,6 +89,38 @@ OC.Settings.Apps = OC.Settings.Apps || {
applist.last().after(app);
}
return app;
},
removeNavigation: function(appid){
$.getJSON(OC.filePath('core','ajax','navigationdetect.php'), {app: appid}).done(function(response){
if(response.status === 'success'){
var navIds=response.nav_ids;
for(var i=0; i< navIds.length; i++){
$('#apps').children('li[data-id="'+navIds[i]+'"]').remove();
}
}
});
},
addNavigation: function(appid){
$.getJSON(OC.filePath('core','ajax','navigationdetect.php'), {app: appid}).done(function(response){
if(response.status === 'success'){
var navEntries=response.nav_entries;
for(var i=0; i< navEntries.length; i++){
var entry = navEntries[i];
var container = $('#apps');
if(container.children('li[data-id="'+entry.id+'"]').length === 0){
var li=$('<li></li>');
li.attr('data-id', entry.id);
var a=$('<a></a>');
a.attr('style', 'background-image: url('+entry.icon+')');
a.text(entry.name);
a.attr('href', entry.href);
li.append(a);
container.append(li);
}
}
}
});
}
};