Sindbad~EG File Manager
<?php
/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2022 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/
/**
* @since 9.1
*/
/**
* Itemlock is dedicated to manage real-time lock of items in GLPI.
*
* Item locks are used to lock items like Ticket, Computer, Reminder, ..., see list in $CFG_GLPI['lock_lockable_objects']
*
* @author Olivier Moron
* @since 9.1
*
**/
class ObjectLock extends CommonDBTM
{
private $itemtype = "";
private $itemtypename = "";
private $itemid = 0;
private static $shutdownregistered = false;
/**
* @see CommonGLPI::getTypeName()
*/
public static function getTypeName($nb = 0)
{
return _n('Object Lock', 'Object Locks', $nb);
}
/**
* Summary of __construct
*
* @param $locitemtype (default ObjectLoc
* @param $locitemid (default 0)
**/
public function __construct($locitemtype = 'ObjectLock', $locitemid = 0)
{
$this->itemtype = $locitemtype;
$this->itemid = $locitemid;
$this->itemtypename = $locitemtype::getTypeName(1);
}
/**
* Summary of getEntityID
* @return 0
**/
public function getEntityID()
{
return 0;
}
/**
* Summary of getLockableObjects
*
* @return array of lockable objects 'itemtype' => 'plural itemtype'
**/
public static function getLockableObjects()
{
global $CFG_GLPI;
$ret = [];
foreach ($CFG_GLPI['lock_lockable_objects'] as $lo) {
$ret[$lo] = $lo::getTypeName(Session::getPluralNumber());
}
asort($ret, SORT_STRING);
return $ret;
}
/**
* Summary of autoLockMode
* Manages autolock mode
*
* @return bool: true if read-only profile lock has been set
**/
private function autoLockMode()
{
// if !autolock mode then we are going to view the item with read-only profile
// if isset($_POST['lockwrite']) then will behave like if automode was true but for this object only and for the lifetime of the session
// look for lockwrite request
if (isset($_POST['lockwrite'])) {
$_SESSION['glpilock_autolock_items'][ $this->itemtype ][$this->itemid] = 1;
}
$ret = isset($_SESSION['glpilock_autolock_items'][ $this->itemtype ][ $this->itemid ])
|| $_SESSION['glpilock_autolock_mode'] == 1;
$locked = $this->getLockedObjectInfo();
if (!$ret && !$locked) {
// open the object using read-only profile
self::setReadonlyProfile();
$this->setReadOnlyMessage();
}
return $ret || $locked;
}
/**
* Summary of getScriptToUnlock
*/
private function getScriptToUnlock()
{
global $CFG_GLPI;
$ret = Html::scriptBlock("
function unlockIt(obj) {
function callUnlock( ) {
$.post({
url: '" . $CFG_GLPI['root_doc'] . "/ajax/unlockobject.php',
cache: false,
data: {
unlock: 1,
force: 1,
id: {$this->fields['id']}
},
dataType: 'json',
success: function( data, textStatus, jqXHR ) { " .
Html::jsConfirmCallback(__('Reload page?'), __('Item unlocked!'), "function() {
window.location.reload(true);
}") . "
},
error: function() { " .
Html::jsAlertCallback(__('Contact your GLPI admin!'), __('Item NOT unlocked!')) . "
}
});
}" .
Html::jsConfirmCallback(__('Force unlock this item?'), $this->itemtypename . " #" . $this->itemid, "callUnlock") . "
}");
return $ret;
}
/**
* Summary of getForceUnlockMessage
* @return string '' if no rights to unlock type,
* else html @see getForceUnlockButton
*/
private function getForceUnlockMessage()
{
if (isset($_SESSION['glpilocksavedprofile']) && ($_SESSION['glpilocksavedprofile'][strtolower($this->itemtype)] & UNLOCK)) {
echo $this->getScriptToUnlock();
return $this->getForceUnlockButton();
}
return '';
}
private function getForceUnlockButton()
{
$msg = "<a class='btn btn-sm btn-primary ms-2' onclick='javascript:unlockIt(this);'>
<i class='fas fa-unlock'></i>
<span>" . sprintf(__('Force unlock %1s #%2s'), $this->itemtypename, $this->itemid) . "</span>
</a>";
return $msg;
}
/**
* Summary of setLockedByYouMessage
* Shows 'Locked by You!' message and proposes to unlock it
**/
private function setLockedByYouMessage()
{
echo $this->getScriptToUnlock();
$msg = "<strong class='nowrap'>";
$msg .= __("Locked by you!");
$msg .= $this->getForceUnlockButton();
$msg .= "</strong>";
$this->displayLockMessage($msg);
}
/**
* Summary of setLockedByMessage
* Shows 'Locked by ' message and proposes to request unlock from locker
**/
private function setLockedByMessage()
{
global $CFG_GLPI;
// should get locking user info
$user = new User();
$user->getFromDB($this->fields['users_id']);
$useremail = new UserEmail();
$showAskUnlock = $useremail->getFromDBByCrit([
'users_id' => $this->fields['users_id'],
'is_default' => 1
]) && ($CFG_GLPI['notifications_mailing'] == 1);
$userdata = getUserName($this->fields['users_id'], 2);
if ($showAskUnlock) {
$ret = Html::scriptBlock("
function askUnlock() {
" . Html::jsConfirmCallback(__('Ask for unlock this item?'), $this->itemtypename . " #" . $this->itemid, "function() {
$.post({
url: '" . $CFG_GLPI['root_doc'] . "/ajax/unlockobject.php',
cache: false,
data: {
requestunlock: 1,
id: {$this->fields['id']}
},
dataType: 'json',
success: function( data, textStatus, jqXHR ) {
" . Html::jsAlertCallback($userdata['name'], __('Request sent to')) . "
}
});
}") . "
}");
echo $ret;
}
$ret = Html::scriptBlock("
$(function(){
var lockStatusTimer;
$('#alertMe').change(function( eventObject ){
if( this.checked ) {
lockStatusTimer = setInterval( function() {
$.get({
url: '" . $CFG_GLPI['root_doc'] . "/ajax/unlockobject.php',
cache: false,
data: 'lockstatus=1&id=" . $this->fields['id'] . "',
success: function( data, textStatus, jqXHR ) {
if( data == 0 ) {
clearInterval(lockStatusTimer);" .
Html::jsConfirmCallback(__('Reload page?'), __('Item unlocked!'), "function() {
window.location.reload(true);
}") . "
}
}
});
},15000)
} else {
clearInterval(lockStatusTimer);
}
});
});
");
echo $ret;
$msg = "<strong class='nowrap'>";
$msg .= sprintf(__('Locked by %s'), "<a href='" . $user->getLinkURL() . "'>" . $userdata['name'] . "</a>");
$msg .= " " . Html::showToolTip($userdata["comment"], ['link' => $userdata['link'], 'display' => false]);
$msg .= " -> " . Html::convDateTime($this->fields['date']);
$msg .= "</strong>";
if ($showAskUnlock) {
$msg .= "<a class='btn btn-sm btn-primary ms-2' onclick='javascript:askUnlock();'>
<i class='fas fa-unlock'></i>
<span>" . __('Ask for unlock') . "</span>
</a>";
}
$msg .= "<label for='alertMe'>" . __('Alert me when unlocked') . "</label>";
$msg .= Html::getCheckbox(['id' => 'alertMe']);
$msg .= $this->getForceUnlockMessage(); // will get a button to force unlock if UNLOCK rights are in the user's profile
$msg .= "</span>";
$this->displayLockMessage($msg);
}
/**
* Summary of setReadOnlyMessage
* Shows 'Read-only!' message and propose to request a lock on the item
* This function is used by autoLockMode function
**/
private function setReadOnlyMessage()
{
$msg = "<span class=red style='padding-left:5px;'>";
$msg .= __('Warning: read-only!');
$msg .= "</span>";
$msg .= '<form action="' . $_SERVER['REQUEST_URI'] . '" method="POST" style="display:inline;">';
$msg .= Html::hidden('_glpi_csrf_token', ['value' => Session::getNewCSRFToken()]);
$msg .= Html::hidden('lockwrite', ['value' => 1]);
$msg .= '<button type="submit" class="btn btn-sm btn-primary ms-2">';
$msg .= __('Request write on ') . $this->itemtypename . " #" . $this->itemid;
$msg .= '</button>';
$msg .= '</form>';
$this->displayLockMessage($msg);
}
/**
* Summary of lockObject
* Tries to lock object and if yes output code to auto unlock it when leaving browser page.
* If lock can't be set (i.e.: someone has already locked it), LockedBy message is shown accordingly,
* and read-only profile is set
* @return bool: true if locked
**/
private function lockObject()
{
global $CFG_GLPI;
$ret = false;
if (
!($gotIt = $this->getFromDBByCrit(['itemtype' => $this->itemtype,
'items_id' => $this->itemid
]))
&& $id = $this->add(['itemtype' => $this->itemtype,
'items_id' => $this->itemid,
'users_id' => Session::getLoginUserID()
])
) {
// add a script to unlock the Object
echo Html::scriptBlock("$(function() {
$(window).on('beforeunload', function() {
var fallback_request = function() {
$.post({
url: '" . $CFG_GLPI['root_doc'] . "/ajax/unlockobject.php',
async: false,
cache: false,
data: {
unlock: 1,
id: $id
},
dataType: 'json'
});
};
if (typeof window.fetch !== 'undefined') {
fetch('" . $CFG_GLPI['root_doc'] . "/ajax/unlockobject.php', {
method: 'POST',
cache: 'no-cache',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded;',
'X-Glpi-Csrf-Token': getAjaxCsrfToken()
},
body: 'unlock=1&id={$id}'
}).catch(function(error) {
//fallback if fetch fails
fallback_request();
});
} else {
//fallback for browsers with no fetch support
fallback_request();
}
});
})");
$ret = true;
} else { // can't add a lock as another one is already existing
if (!$gotIt) {
$this->getFromDBByCrit([
'itemtype' => $this->itemtype,
'items_id' => $this->itemid
]);
}
// open the object as read-only as it is already locked by someone
self::setReadonlyProfile();
if ($this->fields['users_id'] != Session::getLoginUserID()) {
$this->setLockedByMessage();
} else {
$this->setLockedByYouMessage();
}
// and if autolock was set for this item then unset it
unset($_SESSION['glpilock_autolock_items'][ $this->itemtype ][ $this->itemid ]);
}
return $ret;
}
/**
* Summary of getLockedObjectInfo
*
* @return bool: true if object is locked, and $this is filled with record from DB
**/
private function getLockedObjectInfo()
{
global $CFG_GLPI;
$ret = false;
if (
$CFG_GLPI["lock_use_lock_item"]
&& ($CFG_GLPI["lock_lockprofile_id"] > 0)
&& Session::getCurrentInterface() == 'central'
&& in_array($this->itemtype, $CFG_GLPI['lock_item_list'])
&& $this->getFromDBByCrit(['itemtype' => $this->itemtype,
'items_id' => $this->itemid
])
) {
$ret = true;
}
return $ret;
}
/**
* Summary of isLocked
*
* @param $itemtype
* @param $items_id
*
* @return bool|ObjectLock: returns ObjectLock if locked, else false
**/
public static function isLocked($itemtype, $items_id)
{
$ol = new self($itemtype, $items_id);
return ($ol->getLockedObjectInfo() ? $ol : false);
}
/**
* Summary of setReadOnlyProfile
* Switches current profile with read-only profile
* Registers a shutdown function to be sure that even in case of die() calls,
* the switch back will be done: to ensure correct reset of normal profile
**/
public static function setReadOnlyProfile()
{
global $CFG_GLPI, $_SESSION;
// to prevent double set ReadOnlyProfile
if (!isset($_SESSION['glpilocksavedprofile'])) {
if (isset($CFG_GLPI['lock_lockprofile'])) {
if (!self::$shutdownregistered) {
// this is a security in case of a die that can prevent correct revert of profile
register_shutdown_function([__CLASS__, 'revertProfile']);
self::$shutdownregistered = true;
}
$_SESSION['glpilocksavedprofile'] = $_SESSION['glpiactiveprofile'];
$_SESSION['glpiactiveprofile'] = $CFG_GLPI['lock_lockprofile'];
// this mask is mandatory to prevent read of information
// that are not permitted to view by active profile
$rights = ProfileRight::getAllPossibleRights();
foreach ($rights as $key => $val) {
if (isset($_SESSION['glpilocksavedprofile'][$key])) {
$_SESSION['glpiactiveprofile'][$key]
= intval($_SESSION['glpilocksavedprofile'][$key])
& (isset($CFG_GLPI['lock_lockprofile'][$key])
? intval($CFG_GLPI['lock_lockprofile'][$key]) : 0);
}
}
// don't forget entities
$_SESSION['glpiactiveprofile']['entities'] = $_SESSION['glpilocksavedprofile']['entities'];
}
}
}
/**
* Summary of revertProfile
* Will revert normal user profile
**/
public static function revertProfile()
{
global $_SESSION;
if (isset($_SESSION['glpilocksavedprofile'])) {
$_SESSION['glpiactiveprofile'] = $_SESSION['glpilocksavedprofile'];
unset($_SESSION['glpilocksavedprofile']);
}
}
/**
* Summary of manageObjectLock
* Is the main function to be called in order to lock an item
*
* @param $itemtype
* @param $options
**/
public static function manageObjectLock($itemtype, &$options)
{
global $CFG_GLPI;
if (isset($options['id']) && ($options['id'] > 0)) {
$ol = new self($itemtype, $options['id']);
$template = (isset($options['withtemplate'])
&& ($options['withtemplate'] > 0) ? true : false);
if (
(Session::getCurrentInterface() == "central")
&& isset($CFG_GLPI["lock_use_lock_item"]) && $CFG_GLPI["lock_use_lock_item"]
&& ($CFG_GLPI["lock_lockprofile_id"] > 0)
&& in_array($itemtype, $CFG_GLPI['lock_item_list'])
&& Session::haveRightsOr($itemtype::$rightname, [UPDATE, DELETE, PURGE, UPDATENOTE])
&& !$template
) {
if (
!$ol->autoLockMode()
|| !$ol->lockObject($options['id'])
) {
$options['locked'] = 1;
}
}
}
}
/**
* Summary of displayLockMessage
* Shows a short message top-left of screen
* This message is permanent, and can't be closed
*
* @param $msg : message to be shown
* @param $title : if $title is '' then title bar it is not shown (default '')
**/
private function displayLockMessage($msg, $title = '')
{
echo Html::scriptBlock("
$(function() {
$(`<div id='message_after_lock' class='objectlockmessage' style='display: inline-block;'>$msg</div>`).insertAfter('.navigationheader');
});
");
}
/**
* @see CommonDBTM::processMassiveActionsForOneItemtype
**/
public static function processMassiveActionsForOneItemtype(
MassiveAction $ma,
CommonDBTM $item,
array $ids
) {
foreach ($ids as $items_id) {
$itemtype = get_class($item);
$lo = new self($itemtype, $items_id);
if ($lo->getLockedObjectInfo()) {
$lo->deleteFromDB();
Log::history($items_id, $itemtype, [0, '', ''], 0, Log::HISTORY_UNLOCK_ITEM);
$ma->itemDone($itemtype, $items_id, MassiveAction::ACTION_OK);
}
}
}
public static function rawSearchOptionsToAdd($itemtype)
{
global $CFG_GLPI;
$tab = [];
if (
(Session::getCurrentInterface() == "central")
&& isset($CFG_GLPI["lock_use_lock_item"]) && $CFG_GLPI["lock_use_lock_item"]
&& ($CFG_GLPI["lock_lockprofile_id"] > 0)
&& in_array($itemtype, $CFG_GLPI['lock_item_list'])
) {
$tab[] = [
'id' => '205',
'table' => 'glpi_users',
'field' => 'name',
'datatype' => 'dropdown',
'right' => 'all',
'name' => __('Locked by'),
'forcegroupby' => true,
'massiveaction' => false,
'joinparams' => [
'jointype' => '',
'beforejoin' => [
'table' => getTableForItemType('ObjectLock'),
'joinparams' => ['jointype' => "itemtype_item"]
]
]
];
$tab[] = [
'id' => '206',
'table' => getTableForItemType('ObjectLock'),
'field' => 'date',
'datatype' => 'datetime',
'name' => __('Locked date'),
'joinparams' => ['jointype' => 'itemtype_item'],
'massiveaction' => false,
'forcegroupby' => true
];
}
return $tab;
}
/**
* Summary of getRightsToAdd
*
* @param $itemtype
* @param $interface (default 'central')
*
* @return array: empty array if itemtype is not lockable; else returns UNLOCK right
**/
public static function getRightsToAdd($itemtype, $interface = 'central')
{
global $CFG_GLPI;
$ret = [];
if (
($interface == "central")
&& isset($CFG_GLPI["lock_use_lock_item"]) && $CFG_GLPI["lock_use_lock_item"]
&& ($CFG_GLPI["lock_lockprofile_id"] > 0)
&& in_array($itemtype, $CFG_GLPI['lock_lockable_objects'])
) {
$ret = [UNLOCK => __('Unlock')];
}
return $ret;
}
/**
* Give cron information
*
* @param $name : task's name
*
* @return array of information
**/
public static function cronInfo($name)
{
switch ($name) {
case 'unlockobject':
return ['description' => __('Unlock forgotten locked objects'),
'parameter' => __('Timeout to force unlock (hours)')
];
}
return [];
}
/**
* Cron for unlocking forgotten locks
*
* @param $task : crontask object
*
* @return integer
* >0 : done
* <0 : to be run again (not finished)
* 0 : nothing to do
**/
public static function cronUnlockObject($task)
{
// here we have to delete old locks
$actionCode = 0; // by default
$task->setVolume(0); // start with zero
$lockedItems = getAllDataFromTable(
getTableForItemType(__CLASS__),
[
'date' => ['<', date("Y-m-d H:i:s", time() - ($task->fields['param'] * HOUR_TIMESTAMP))]
]
);
foreach ($lockedItems as $row) {
$ol = new self();
if ($ol->delete($row)) {
$actionCode++;
$item = new $row['itemtype']();
$item->getFromDB($row['items_id']);
$task->log($row['itemtype'] . " #" . $row['items_id'] . ": " . $item->getLink());
$task->addVolume(1);
Log::history(
$row['items_id'],
$row['itemtype'],
[0, '', ''],
0,
Log::HISTORY_UNLOCK_ITEM
);
} else {
return -1; // error can't delete record, then exit with error
}
}
return $actionCode;
}
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists