Browse Source

Merge remote-tracking branch 'origin/master'

master
Janusz Tylek 2 years ago
parent
commit
24dbbac196
29 changed files with 1321 additions and 709 deletions
  1. +0
    -22
      composer.lock
  2. +41
    -48
      include/session.php
  3. +2
    -2
      index.php
  4. +3
    -10
      modules/Base/Admin/AdminCommon_0.php
  5. +59
    -12
      modules/Base/User/Administrator/AdministratorCommon_0.php
  6. +1
    -1
      modules/CRM/Contacts/ContactsCommon_0.php
  7. +15
    -34
      modules/CRM/Contacts/Contacts_0.php
  8. +29
    -23
      modules/Utils/Attachment/AttachmentCommon_0.php
  9. +119
    -0
      modules/Utils/Attachment/FileActionHandler.php
  10. +6
    -0
      modules/Utils/Attachment/file.php
  11. +63
    -20
      modules/Utils/FileStorage/ActionHandler.php
  12. +26
    -9
      modules/Utils/FileStorage/FileStorageCommon_0.php
  13. +4
    -4
      modules/Utils/GenericBrowser/GenericBrowser_0.php
  14. +35
    -2
      modules/Utils/LeightboxPrompt/LeightboxPrompt_0.php
  15. +11
    -8
      modules/Utils/RecordBrowser/Access.php
  16. +53
    -8
      modules/Utils/RecordBrowser/Crits.php
  17. +9
    -2
      modules/Utils/RecordBrowser/CritsToWords.php
  18. +36
    -28
      modules/Utils/RecordBrowser/RecordBrowserCommon_0.php
  19. +13
    -35
      modules/Utils/RecordBrowser/RecordBrowser_0.php
  20. +5
    -0
      modules/Utils/RecordBrowser/RecordPickerFS/RecordPickerFS_0.php
  21. +125
    -0
      modules/Utils/Tray/Box.php
  22. +248
    -0
      modules/Utils/Tray/Slot.php
  23. +87
    -116
      modules/Utils/Tray/TrayCommon_0.php
  24. +16
    -14
      modules/Utils/Tray/TrayInstall.php
  25. +192
    -172
      modules/Utils/Tray/Tray_0.php
  26. BIN
      modules/Utils/Tray/theme/Thumbs.db
  27. +88
    -71
      modules/Utils/Tray/theme/tray.css
  28. +35
    -46
      modules/Utils/Tray/theme/tray.tpl
  29. +0
    -22
      modules/Utils/Tray/tray.js

+ 0
- 22
composer.lock View File

@@ -2982,28 +2982,6 @@
"tput",
"window"
],
"time": "2017-05-02T12:26:19+00:00"
},
{
"name": "hoa/event",
"version": "1.17.01.13",
"source": {
"type": "git",
"url": "https://github.com/hoaproject/Event.git",
"reference": "6c0060dced212ffa3af0e34bb46624f990b29c54"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hoaproject/Event/zipball/6c0060dced212ffa3af0e34bb46624f990b29c54",
"reference": "6c0060dced212ffa3af0e34bb46624f990b29c54",
"shasum": ""
},
"require": {
"hoa/consistency": "~1.0",
"hoa/exception": "~1.0"
},
"require-dev": {
"hoa/test": "~2.0"
},
"type": "library",
"extra": {


+ 41
- 48
include/session.php View File

@@ -11,7 +11,7 @@ defined("_VALID_ACCESS") || die('Direct access forbidden');

require_once('database.php');

class DBSession {
class DBSession implements SessionHandlerInterface{
const MAX_SESSION_ID_LENGTH = 128;
private static $lifetime;
private static $memcached;
@@ -28,7 +28,7 @@ class DBSession {
return substr($session_id, 0, self::MAX_SESSION_ID_LENGTH);
}

public static function open($path, $name) {
public function open($path, $name) {
self::$lifetime = min(ini_get("session.gc_maxlifetime"),2592000-1); //less then 30 days
switch(SESSION_TYPE) {
case 'file':
@@ -57,12 +57,12 @@ class DBSession {
return true;
}

public static function close() {
public function close() {
//self::gc(self::$lifetime);
return true;
}

public static function read($name) {
public function read($name) {
$name = self::truncated_session_id($name);
//main session
@@ -93,45 +93,44 @@ class DBSession {
}
if($ret) $_SESSION = unserialize($ret);

if(CID!==false) {
if(!is_numeric(CID))
trigger_error('Invalid client id.',E_USER_ERROR);
if(CID===false) return '';
if(!is_numeric(CID))
trigger_error('Invalid client id.',E_USER_ERROR);

if(isset($_SESSION['session_destroyed'][CID])) return '';
if(isset($_SESSION['session_destroyed'][CID])) return '';
switch(self::$session_type) {
case 'file':
$sess_file = rtrim(FILE_SESSION_DIR,'\\/').'/'.FILE_SESSION_TOKEN.$name.'_'.CID;
if(!file_exists($sess_file)) file_put_contents($sess_file,'');
self::$session_client_fp = fopen($sess_file,'r+');
if(!READ_ONLY_SESSION && !flock(self::$session_client_fp,LOCK_EX))
trigger_error('Unable to get lock on session file='.$sess_file,E_USER_ERROR);
$ret = stream_get_contents(self::$session_client_fp);
break;
case 'memcache':
if(!READ_ONLY_SESSION && !self::$memcached->lock(MEMCACHE_SESSION_TOKEN.$name.'_'.CID,self::$memcached_lock_time))
trigger_error('Unable to get lock on session mem='.$name.'_'.CID,E_USER_ERROR);
$ret = '';
for($i=0;; $i++) {
$rr = self::$memcached->get(MEMCACHE_SESSION_TOKEN.$name.'_'.CID.'/'.$i);
if($rr==='' || $rr===false || $rr===null) break;
$ret .= $rr;
}
break;
case 'sql':
$ret = DB::GetCol('SELECT data FROM session_client WHERE session_name = %s AND client_id=%d'.(READ_ONLY_SESSION?'':' FOR UPDATE'), array($name, CID));
if($ret) $ret = $ret[0];
break;
}
if($ret) $_SESSION['client'] = unserialize($ret);

if(!isset($_SESSION['client']['__module_vars__']))
$_SESSION['client']['__module_vars__'] = array();
switch(self::$session_type) {
case 'file':
$sess_file = rtrim(FILE_SESSION_DIR,'\\/').'/'.FILE_SESSION_TOKEN.$name.'_'.CID;
if(!file_exists($sess_file)) file_put_contents($sess_file,'');
self::$session_client_fp = fopen($sess_file,'r+');
if(!READ_ONLY_SESSION && !flock(self::$session_client_fp,LOCK_EX))
trigger_error('Unable to get lock on session file='.$sess_file,E_USER_ERROR);
$ret = stream_get_contents(self::$session_client_fp);
break;
case 'memcache':
if(!READ_ONLY_SESSION && !self::$memcached->lock(MEMCACHE_SESSION_TOKEN.$name.'_'.CID,self::$memcached_lock_time))
trigger_error('Unable to get lock on session mem='.$name.'_'.CID,E_USER_ERROR);
$ret = '';
for($i=0;; $i++) {
$rr = self::$memcached->get(MEMCACHE_SESSION_TOKEN.$name.'_'.CID.'/'.$i);
if($rr==='' || $rr===false || $rr===null) break;
$ret .= $rr;
}
break;
case 'sql':
$ret = DB::GetCol('SELECT data FROM session_client WHERE session_name = %s AND client_id=%d'.(READ_ONLY_SESSION?'':' FOR UPDATE'), array($name, CID));
if($ret) $ret = $ret[0];
break;
}
return '';
if($ret) $_SESSION['client'] = unserialize($ret);

if(!isset($_SESSION['client']['__module_vars__']))
$_SESSION['client']['__module_vars__'] = array();
}

public static function write($name, $data) {
public function write($name, $data) {
if(READ_ONLY_SESSION) return true;
$name = self::truncated_session_id($name);
if(defined('SESSION_EXPIRED')) {
@@ -207,7 +206,7 @@ class DBSession {
break;
}

return ($ret>0)?true:false;
return $ret > 0;
}
public static function destroy_client($name,$i) {
@@ -229,7 +228,7 @@ class DBSession {
DB::Execute('DELETE FROM history WHERE session_name=%s AND client_id=%d',array($name,$i));
}

public static function destroy($name) {
public function destroy($name) {
$name = self::truncated_session_id($name);
$cids = DB::GetCol('SELECT DISTINCT client_id FROM history WHERE session_name=%s',array($name));
foreach($cids as $i)
@@ -254,7 +253,7 @@ class DBSession {
return true;
}

public static function gc($lifetime) {
public function gc($lifetime) {
$t = time()-$lifetime;
$ret = DB::Execute('SELECT name FROM session WHERE expires <= %d',array($t));
while($row = $ret->FetchRow()) {
@@ -323,7 +322,6 @@ class EpesiMemcache {

// remember that even with SET_SESSION = false, class defined below is declared
if(!SET_SESSION) {
global $_SESSION;
if(!isset($_SESSION) || !is_array($_SESSION))
$_SESSION = array();
return;
@@ -336,12 +334,7 @@ if(defined('EPESI_PROCESS')) {
ini_set('session.gc_probability', 0);
}

session_set_save_handler(array('DBSession','open'),
array('DBSession','close'),
array('DBSession','read'),
array('DBSession','write'),
array('DBSession','destroy'),
array('DBSession','gc'));
session_set_save_handler(new DBSession());

if(extension_loaded('apc') || extension_loaded('eaccelerator') || extension_loaded('xcache')) //fix for class DBSession not found
register_shutdown_function('session_write_close');


+ 2
- 2
index.php View File

@@ -10,8 +10,8 @@
* @version 1.0
* @package epesi-base
*/
if(version_compare(phpversion(), '5.5.0')==-1)
die("You are running an old version of PHP, php 5.5 required.");
if(version_compare(phpversion(), '7.0.0')==-1)
die("You are running an old version of PHP, php 7.0 required.");

if(trim(ini_get("safe_mode")))
die('You cannot use EPESI with PHP safe mode turned on - please disable it. Please notice this feature is deprecated since PHP 5.3 and will be removed in PHP 7.0.');


+ 3
- 10
modules/Base/Admin/AdminCommon_0.php View File

@@ -36,7 +36,7 @@ class Base_AdminCommon extends ModuleCommon {
}
public static function get_access($module, $section='', $force_check=false) {
if (!$force_check && Base_AclCommon::i_am_sa()) return true;
if (!$force_check && Acl::i_am_sa()) return true;
static $cache = array();
if (!isset($cache[$module])) {
$cache[$module] = array();
@@ -47,21 +47,14 @@ class Base_AdminCommon extends ModuleCommon {
if ($raws==false) {
$defaults[''] = $raws;
} else {
$defaults[''] = 1;
if (is_array($raws))
foreach ($raws as $s=>$v) {
if (isset($v['default']))
$defaults[$s] = $v['default'];
else
$defaults[$s] = 0;
$defaults[$s] = $v['default']?? 0;
}
}
}
foreach($defaults as $s=>$v)
if (isset($ret[$s]))
$cache[$module][$s] = $ret[$s];
else
$cache[$module][$s] = $v;
$cache[$module][$s] = $ret[$s]?? $v;
}
return $cache[$module][$section];
}


+ 59
- 12
modules/Base/User/Administrator/AdministratorCommon_0.php View File

@@ -14,29 +14,76 @@ defined("_VALID_ACCESS") || die('Direct access forbidden');

class Base_User_AdministratorCommon extends Base_AdminModuleCommon {
public static function user_settings() {
if(Base_AclCommon::i_am_user()) return array(__('Account')=>'body');
return array();
return Acl::i_am_user() ? [
__('Account') => 'body'
]: [];
}
public static function admin_caption() {
return array('label'=>__('Manage users'), 'section'=>__('User Management'));
return [
'label' => __('Manage users'),
'section' => __('User Management')
];
}
public static function admin_access() {
return DEMO_MODE?false:true;
return !DEMO_MODE;
}

public static function admin_access_levels() {
return array(
'log_as_user' => array('label' => __('Allow admin to login as user'), 'default' => 1),
'log_as_admin' => array('label' => __('Allow admin to login as other admin'), 'default' => 0),
'manage_ban' => array('label' => __('Allow admin to manage ban options and autologin'), 'default' => 0)
);
}
return [
'log_as_user' => [
'label' => __('Allow admin to login as user'),
'default' => 1
],
'log_as_admin' => [
'label' => __('Allow admin to login as other admin'),
'default' => 0
],
'manage_ban' => [
'label' => __('Allow admin to manage ban options and autologin'),
'default' => 0
]
];
}
public static function menu() {
if (!Base_AclCommon::check_permission('Advanced User Settings'))
return array(_M('My settings')=>array('__weight__'=>10,'__submenu__'=>1,_M('Change password')=>array()));
if (! Acl::check_permission('Advanced User Settings')) return [
_M('My settings') => [
'__weight__' => 10,
'__submenu__' => 1,
_M('Change password') => []
]
];
}
public static function get_admin_access($level) {
if (!Acl::i_am_admin()) return false;
if (Acl::i_am_sa()) return true;
if (!in_array($level, array_keys(self::admin_access_levels()))) return false;
return Base_AdminCommon::get_access(Base_User_Administrator::class, $level);
}
public static function get_log_as_user_access($user) {
static $admin_levels = false;
static $my_level = false;
if (!Acl::i_am_admin()) return false;
if (Acl::i_am_sa()) return true;
if ($admin_levels === false)
$admin_levels = DB::GetAssoc('SELECT id, admin FROM user_login');
if ($my_level === false)
$my_level = $admin_levels[Acl::get_user()]?? 0;
$user_level = $admin_levels[$user]?? 0;
return $user_level == 0 && self::get_admin_access('log_as_user') || // contact is user and I can login as user
$user_level == 1 && self::get_admin_access('log_as_admin');
}
}
?>

+ 1
- 1
modules/CRM/Contacts/ContactsCommon_0.php View File

@@ -853,7 +853,7 @@ class CRM_ContactsCommon extends ModuleCommon {
if (!Base_AclCommon::i_am_admin()) return;
if ($mode=='view') {
if (!$default) return;
if(Base_AclCommon::i_am_sa()) {
if(Base_User_AdministratorCommon::get_log_as_user_access($default)) {
Base_ActionBarCommon::add('settings', __('Log as user'), Module::create_href(array('log_as_user'=>$default)));
if (isset($_REQUEST['log_as_user']) && $_REQUEST['log_as_user']==$default) {
Acl::set_user($default, true); //tag who is logged


+ 15
- 34
modules/CRM/Contacts/Contacts_0.php View File

@@ -119,47 +119,28 @@ class CRM_Contacts extends Module {

public function change_email_header() {
$adm = $this->init_module('Base_User_Administrator');
$back = $adm->is_back();
if ($back) {
Base_BoxCommon::pop_main();
return false;
if ($adm->is_back()) {
return Base_BoxCommon::pop_main();
}
$result = $this->display_module($adm, array(), 'change_email_header');
$this->display_module($adm, array(), 'change_email_header');
print('<span style="display:none;">'.microtime(true).'</span>');
return true;
}
public function user_actions($r, $gb_row) {
static $admin_levels = false;
static $my_level = false;
if ($admin_levels === false)
$admin_levels = DB::GetAssoc('SELECT id,admin FROM user_login');
if ($my_level === false)
$my_level = isset($admin_levels[Base_AclCommon::get_user()])
? $admin_levels[Base_AclCommon::get_user()] : 0;

$mod = 'Base_User_Administrator';
$log_as_user = Base_AdminCommon::get_access($mod, 'log_as_user');
$log_as_admin = Base_AdminCommon::get_access($mod, 'log_as_admin');
public function user_actions($contact, $gb_row) {
if (!Base_User_AdministratorCommon::get_log_as_user_access($contact['login'])) return;
$user_level = isset($admin_levels[$r['login']]) ? $admin_levels[$r['login']] : 0;
// 2 is superadmin, 1 admin, 0 user
if ($my_level == 2 || // i am super admin or...
$my_level == 1 && // i am admin and...
($user_level == 0 && $log_as_user || // contact is user and I can login as user
$user_level == 1 && $log_as_admin)) { // contact is admin and I can login as admin
if (Base_UserCommon::is_active($r['login'])) {
$gb_row->add_action($this->create_callback_href(array($this, 'change_user_active_state'), array($r['login'], false)), 'Deactivate user', null, Base_ThemeCommon::get_template_file('Utils_GenericBrowser', 'active-on.png'));
$gb_row->add_action(Module::create_href(array('log_as_user' => $r['login'])), 'Log as user', null, Base_ThemeCommon::get_template_file('Utils_GenericBrowser', 'restore.png'));
// action!
if (isset($_REQUEST['log_as_user']) && $_REQUEST['log_as_user'] == $r['login']) {
Acl::set_user($r['login'], true);
Epesi::redirect();
return;
}
} else {
$gb_row->add_action($this->create_callback_href(array($this, 'change_user_active_state'), array($r['login'], true)), 'Activate user', null, Base_ThemeCommon::get_template_file('Utils_GenericBrowser', 'active-off.png'));
if (Base_UserCommon::is_active($contact['login'])) {
$gb_row->add_action($this->create_callback_href(array($this, 'change_user_active_state'), array($contact['login'], false)), __('Deactivate user'), null, Base_ThemeCommon::get_template_file('Utils_GenericBrowser', 'active-on.png'));
$gb_row->add_action(Module::create_href(array('log_as_user' => $contact['login'])), __('Log as user'), null, Base_ThemeCommon::get_template_file('Utils_GenericBrowser', 'restore.png'));
// action!
if (isset($_REQUEST['log_as_user']) && $_REQUEST['log_as_user'] == $contact['login']) {
Acl::set_user($contact['login'], true);
Epesi::redirect();
return;
}
} else {
$gb_row->add_action($this->create_callback_href(array($this, 'change_user_active_state'), array($contact['login'], true)), 'Activate user', null, Base_ThemeCommon::get_template_file('Utils_GenericBrowser', 'active-off.png'));
}
}
public function change_user_active_state($user, $state) {


+ 29
- 23
modules/Utils/Attachment/AttachmentCommon_0.php View File

@@ -142,25 +142,24 @@ class Utils_AttachmentCommon extends ModuleCommon {

public static function get_files($group=null,$group_starts_with=false) {
$ids = self::get_where($group,$group_starts_with);
if(!$ids) return array();
$files = array();
foreach($ids as $id) {
$note = self::get_note($id);
foreach($note['files'] as $fsid) {
$meta = Utils_FileStorageCommon::meta($fsid);
$files[] = array_merge($meta, $note, array(
'id' => $fsid,
'note_id' => $id,
'file_id' => null,
'upload_by' => $meta['created_by'],
'upload_on' => $meta['created_by'],
'original' => $meta['filename'],
'filestorage_id' => $fsid,
'downloads' => Utils_FileStorageCommon::get_downloads_count($fsid),
));
}
}
if(!$ids) return [];
$files = [];
foreach (self::get_notes(['id' => $ids]) as $id => $note) {
foreach($note['files']?? [] as $fsid) {
$meta = Utils_FileStorageCommon::meta($fsid);
$files[] = array_merge($meta, $note, array(
'id' => $fsid,
'note_id' => $id,
'file_id' => null,
'upload_by' => $meta['created_by'],
'upload_on' => $meta['created_by'],
'original' => $meta['filename'],
'filestorage_id' => $fsid,
'downloads' => Utils_FileStorageCommon::get_downloads_count($fsid),
));
}
}
return $files;
}

@@ -280,10 +279,13 @@ class Utils_AttachmentCommon extends ModuleCommon {
self::$mark_as_read = array();
}

$text = (!$view?'<b style="float:left;margin-right:30px;">'.$row['title'].'</b> ':''). $text . self::display_files($row, $nolink);
$text = (!$view && $row['title']?'<b style="float:left;margin-right:30px;">'.$row['title'].'</b> ':''). $text;
if($row['sticky']) $text = '<img src="'.Base_ThemeCommon::get_template_file('Utils_Attachment','sticky.png').'" hspace=3 align="left"> '.$text;

return $text;
$files = self::display_files($row, $nolink);
return implode('<br><br>', array_filter([$text, $files]));
}
public static function display_files($row, $nolink = false, $desc = null, $tab = null) {
@@ -303,8 +305,8 @@ class Utils_AttachmentCommon extends ModuleCommon {
}
}
$inline_nodes = array_filter($inline_nodes);
return implode('<br>', $labels) . ($inline_nodes? '<hr>': '') . implode('<hr>', $inline_nodes);
return implode('<br>', $labels) . ($inline_nodes? '<hr>': '') . implode('&nbsp;', $inline_nodes);
}

public static function description_callback($row,$nolink=false) {
@@ -610,6 +612,10 @@ class Utils_AttachmentCommon extends ModuleCommon {
}
return $cache[$id];
}
public static function get_notes($crits = array(), $cols = array(), $order = array(), $limit = array(), $admin = false) {
return Utils_RecordBrowserCommon::get_records('utils_attachment', $crits, $cols, $order, $limit, $admin);
}

/**
* Create new watchdog event for record if $group denotes record.


+ 119
- 0
modules/Utils/Attachment/FileActionHandler.php View File

@@ -0,0 +1,119 @@
<?php

require_once __DIR__ . '/../RecordBrowser/FileActionHandler.php';

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class Utils_Attachment_FileActionHandler
extends Utils_RecordBrowser_FileActionHandler
{
protected function getHandlingScript()
{
return 'modules/Utils/Attachment/file.php';
}

/**
* Get Action urls for RB file leightbox
*
* @param int $filestorageId Filestorage ID
* @param string $tab Recordset name. e.g. company
* @param int $recordId Record ID
* @param string $field Field identifier. e.g. company_name
* @param string $crypted If file is crypted or not
*
* @return array
*/
public function getActionUrlsAttachment($filestorageId, $tab, $recordId, $field, $crypted)
{
$params = ['tab' => $tab, 'record' => $recordId, 'field' => $field, 'crypted' => $crypted, 'cid' => CID];
return $this->getActionUrls($filestorageId, $params);
}
protected function hasAccess($action, $request)
{
$crypted = $request->get('crypted');
$recordId = $request->get('record');
if ($crypted && !isset($_SESSION['client']['cp'.$recordId]))
return false;
return parent::hasAccess($action, $request);
}
protected function getFile(Request $request, $disposition)
{
$filestorageId = $request->get('id');
$action = $request->get('action');
$crypted = $request->get('crypted');
$recordId = $request->get('record');
$filePack = is_array($filestorageId);
try {
$filestorageIds = is_array($filestorageId)? $filestorageId: array($filestorageId);
if ($filePack) {
$zipFilename = tempnam('tmp', 'zip');
$zip = new ZipArchive();
//create the file and throw the error if unsuccessful
if ($zip->open($zipFilename, ZIPARCHIVE::OVERWRITE )!==true)
throw new Utils_FileStorage_Exception("cannot open $zipFilename for writing - contact with administrator");
}
$size = 0;
foreach ($filestorageIds as $filestorageId) {
$meta = Utils_FileStorageCommon::meta($filestorageId);
if (!$filePack && $action == 'inline' && ($thumbnail = $this->createThumbnail($meta))) {
$mime = $thumbnail['mime'];
$filename = $thumbnail['filename'];
$buffer = $thumbnail['contents'];
}
else {
$mime = Utils_FileStorageCommon::get_mime_type($meta['file'], $meta['filename']);
$filename = $meta['filename'];
$buffer = Utils_FileStorageCommon::read_content($filestorageId);
if($crypted) {
$buffer = Utils_AttachmentCommon::decrypt($buffer, $_SESSION['client']['cp'.$recordId]);
if ($buffer===false) throw new Utils_FileStorage_Exception('File decryption error');
}
}
$size += filesize($meta['file']);
@ini_set('memory_limit',ceil($size*2/1024/1024+64).'M');
if ($filePack)
$zip->addFromString($filename, $buffer);
}
if ($filePack)
$zip->close();
} catch (Utils_FileStorage_Exception $ex) {
return Acl::i_am_admin()? new Response($ex->getMessage(), 400): false;
}
$this->logFileAccessed($filestorageId, $action);
if ($filePack) {
ob_start();
$fp = fopen($zipFilename, 'rb');
while (!feof($fp)) {
print fread($fp, 1024);
}
fclose($fp);
@unlink($zipFilename);
$buffer = ob_get_clean();
$response = $this->createFileResponse($buffer, 'application/zip', 'attachment', "note_$recordId.zip", true);
}
else {
$response = $this->createFileResponse($buffer, $mime, $disposition, $filename);
}
return $response;
}
}

+ 6
- 0
modules/Utils/Attachment/file.php View File

@@ -0,0 +1,6 @@
<?php

require_once __DIR__ . '/FileActionHandler.php';

$handler = new Utils_Attachment_FileActionHandler();
$handler->handle()->send();

+ 63
- 20
modules/Utils/FileStorage/ActionHandler.php View File

@@ -10,9 +10,9 @@ class Utils_FileStorage_ActionHandler
*
* @var array Possible actions to execute
*/
protected $allowedActions = ['download', 'preview', 'remote'];
protected $allowedActions = ['download', 'preview', 'inline', 'remote'];

const actions = ['download'=>0, 'preview'=>1, 'remote'=>2];
const actions = ['download'=>0, 'preview'=>1, 'inline' => 1, 'remote'=>2];

/**
* You can override this variable to allow access for not logged in users
@@ -34,7 +34,7 @@ class Utils_FileStorage_ActionHandler

protected function getHandlingScript()
{
return 'modules/Utils/FileStorage/file.php';
return get_epesi_url() . 'modules/Utils/FileStorage/file.php';
}

protected function getRemoteScript()
@@ -94,7 +94,8 @@ class Utils_FileStorage_ActionHandler
case 'download':
return $this->getFile($request, 'attachment');
case 'preview':
return $this->getFile($request, 'inline');
case 'inline':
return $this->getFile($request, 'inline');
case 'remote':
switch($method) {
case Request::METHOD_POST:
@@ -109,34 +110,76 @@ class Utils_FileStorage_ActionHandler
protected function getFile(Request $request, $disposition)
{
$filestorageId = $request->get('id');
$type = $request->get('action');
$action = $request->get('action');
try {
$meta = Utils_FileStorageCommon::meta($filestorageId);
$buffer = Utils_FileStorageCommon::read_content($filestorageId);

if ($action == 'inline' && ($thumbnail = $this->createThumbnail($meta))) {
$mime = $thumbnail['mime'];
$filename = $thumbnail['filename'];
$buffer = $thumbnail['contents'];
}
else {
$mime = Utils_FileStorageCommon::get_mime_type($meta['file'], $meta['filename']);
$filename = $meta['filename'];
$buffer = Utils_FileStorageCommon::read_content($filestorageId);
}
} catch (Utils_FileStorage_Exception $ex) {
if (Base_AclCommon::i_am_admin()) {
return new Response($ex->getMessage(), 400);
}
return false;
}
$type = self::actions[$type];

$remote_address = get_client_ip_address();
$remote_host = gethostbyaddr($remote_address);
DB::Execute('INSERT INTO utils_filestorage_access(file_id,date_accessed,accessed_by,type,ip_address,host_name) '.
'VALUES (%d,%T,%d,%d,%s,%s)',array($filestorageId,time(),Acl::get_user()?Acl::get_user():0,$type,$remote_address,$remote_host));
$this->logFileAccessed($filestorageId, $action);

$mime = Utils_FileStorageCommon::get_mime_type($meta['file'], $meta['filename']);

$response = new Response();
$response->setContent($buffer);
$response->headers->set('Content-Type', $mime);
$response->headers->set('Content-Length', strlen($buffer));
$response->headers->set('Content-Disposition', "$disposition; filename=\"$meta[filename]\"");
return $response;
return $this->createFileResponse($buffer, $mime, $disposition, $filename);
}
protected function createFileResponse($content, $mime, $disposition, $filename, $nocache = false) {
$response = new Response();
$response->setContent($content);
$response->headers->set('Content-Type', $mime);
$response->headers->set('Content-Length', strlen($content));
$response->headers->set('Content-Disposition', "$disposition; filename=\"$filename\"");
if ($nocache) {
$response->headers->set('Pragma', 'no-cache');
$response->headers->set('Expires', '0');
}
return $response;
}
protected function logFileAccessed($filestorageId, $action, $time = null) {
$remote_address = get_client_ip_address();
$remote_host = gethostbyaddr($remote_address);
DB::Execute('INSERT INTO utils_filestorage_access(file_id,date_accessed,accessed_by,type,ip_address,host_name) ' . 'VALUES (%d,%T,%d,%d,%s,%s)', [
$filestorageId,
$time ?: time(),
Acl::get_user() ?: 0,
self::actions[$action],
$remote_address,
$remote_host
]);
}

protected function createThumbnail($meta)
{
if (!Utils_FileStorageCommon::get_pdf_thumbnail_possible($meta)) return false;
$image = new Imagick($meta['file'] . '[0]');
$image->setImageFormat('jpg');
$mime = 'image/jpeg';
$filename = 'preview.jpeg';
$contents = $image . '';
return compact('mime', 'filename', 'contents');
}
protected function createRemote(Request $request)
{
$params = $request->query->all();


+ 26
- 9
modules/Utils/FileStorage/FileStorageCommon_0.php View File

@@ -37,7 +37,7 @@ class Utils_FileStorageCommon extends ModuleCommon {
*
* @return string File label with link
*/
public static function get_file_label($id, $nolink = false, $icon = true, $action_urls = null)
public static function get_file_label($id, $nolink = false, $icon = true, $action_urls = null, $label = null, $inline = false)
{
$file_exists = self::file_exists($id, false);
@@ -50,16 +50,17 @@ class Utils_FileStorageCommon extends ModuleCommon {
$icon_img = '';
}
$filename = '';
$meta = null;
try {
$meta = is_numeric($id) ? self::meta($id) : $id;
$filename = $meta['filename'];
} catch (Exception $e) {
}
if (!$filename = $label) {
$filename = ($meta['filename']?? '') ?: htmlspecialchars('<' . __('missing filename') . '>');
}
$filename = $filename ?: htmlspecialchars('<' . __('missing filename') . '>');
if ($nolink) {
if ($nolink || !$meta) {
return $filename . ($file_exists ? '': ' [' . __('missing file') . ']');
}
@@ -78,10 +79,13 @@ class Utils_FileStorageCommon extends ModuleCommon {
$link_href = Utils_TooltipCommon::open_tag_attrs($tooltip_text);
}
}
return '<div class="file_link"><a ' . $link_href . '>' . $icon_img . '<span class="file_name">' . $filename . '</span></a></div>';
$ret = '<a ' . $link_href . '>' . $icon_img . '<span class="file_name">' . $filename . '</span></a>';
return $inline? $ret: '<div class="file_link">'.$ret.'</div>';
}
public static function get_file_inline_node($id, $action_urls = null)
public static function get_file_inline_node($id, $action_urls = null, $max_width = '200px')
{
if (!self::file_exists($id, false)) return '';
@@ -90,15 +94,22 @@ class Utils_FileStorageCommon extends ModuleCommon {
if ($action_urls === null) {
$action_urls = self::get_default_action_urls($meta['id']);
}
$max_width .= is_numeric($max_width)? 'px': '';

$type = self::get_mime_type($meta['file'], $meta['filename'], null, false);
switch ($type) {
case 'application/pdf':
if (!self::get_pdf_thumbnail_possible($meta)) {
$ret = '';
break;
}
// image
case 'image/jpeg':
case 'image/gif':
case 'image/png':
case 'image/bmp':
$ret = '<a href="' . $action_urls['preview'] . '" target="_blank"><img src="' . $action_urls['preview'] . '" class="file_inline" style="max-width: 100%" /></a>';
$ret = '<a href="' . $action_urls['preview'] . '" target="_blank"><img src="' . $action_urls['inline'] . '" class="file_inline" style="max-width: ' . $max_width . '" /></a>';
break;

default:
@@ -109,6 +120,12 @@ class Utils_FileStorageCommon extends ModuleCommon {
return $ret;
}
public static function get_pdf_thumbnail_possible($meta) {
$mime = Utils_FileStorageCommon::get_mime_type($meta['file'], $meta['filename'], null, false);

return $mime == 'application/pdf' && class_exists('Imagick');
}
public static function get_default_action_urls($meta) {
$id = is_numeric($meta)? $meta: $meta['id'];


+ 4
- 4
modules/Utils/GenericBrowser/GenericBrowser_0.php View File

@@ -194,10 +194,10 @@ class Utils_GenericBrowser extends Module {
public function __add_row_action($num,$tag_attrs,$label,$tooltip,$icon,$order=0,$off=false,$size=1) {
if (!isset($icon)) $icon = strtolower(trim($label));
switch ($icon) {
case 'view': $order = -3; break;
case 'edit': $order = -2; break;
case 'delete': $order = -1; break;
case 'info': $order = 1000; break;
case 'view': $order = $order?: -3; break;
case 'edit': $order = $order?: -2; break;
case 'delete': $order = $order?: -1; break;
case 'info': $order = $order?: 1000; break;
}
$this->actions[$num][$icon] = array('tag_attrs'=>$tag_attrs,'label'=>$label,'tooltip'=>$tooltip, 'off'=>$off, 'size'=>$size, 'order'=>$order);
$this->en_actions = true;


+ 35
- 2
modules/Utils/LeightboxPrompt/LeightboxPrompt_0.php View File

@@ -33,7 +33,36 @@ class Utils_LeightboxPrompt extends Module {
$this->options[$key] = array('icon'=>$icon, 'form'=>$form, 'label'=>$label, 'tooltip'=>$tooltip);
if (isset($form) && $form->exportValue('submited') && !$form->validate()) $this->open();
//calling open method causes lbp to not be opened by the links on the page in case of no form validation
//this way works because no init is called
if (isset($form) && $form->exportValue('submited') && !$form->validate()) Utils_LeightboxPromptCommon::open($this->group, $this->get_params([]));
}
public function add_options($options) {
foreach ($options as $option => $desc) {
$desc['label'] = $desc['label']?? $option;
$desc['active'] = $desc['active']?? true;
$desc['active'] = is_array($desc['active'])? $desc['active']: [$desc['active']];
if (!(bool) array_product($desc['active'])) continue;
$form = null;
if ($desc['elements']?? []) {
$form = $this->init_module(Libs_QuickForm::module_name());
$elements = array_filter($desc['elements'], function($element) {
return $element['active']?? true;
});
$form->add_array($elements);
$form->setDefaults($desc['defaults']?? []);
}
$this->add_option($option, $desc['label'], $desc['icon']?? null, $form, $desc['tip']?? null);
}
}
public function set_selected_option($option) {
@@ -172,6 +201,10 @@ class Utils_LeightboxPrompt extends Module {
if (!$this->init) print('<a style="display:none;" '.$this->get_href().'></a>');
$this->init=true;
}
public function get_options_count() {
return count($this->options);
}

public function get_close_leightbox_href($reset_view = false) {
return 'href="javascript:void(0)" onclick="' . $this->get_close_leightbox_href_js($reset_view) . '"';
@@ -186,7 +219,7 @@ class Utils_LeightboxPrompt extends Module {
foreach ($this->options as $option_key=>$option) {
if ($option['form']!==null && $option['form']->validate()) {
$ret['option'] = $option_key;
$vals = $option['form']->exportValues();
$vals = array_merge($option['form']->exportValues(), Utils_FileUpload_Dropzone::export_values($option['form']));
if (is_array($this->params_list)) foreach ($this->params_list as $p) {
$ret['params'][$p] = $vals[$this->group.'_'.$p];
unset($vals[$this->group.'_'.$p]);


+ 11
- 8
modules/Utils/RecordBrowser/Access.php View File

@@ -73,7 +73,7 @@ class Utils_RecordBrowser_Access

protected function getCritsRaw()
{
if ($this->isFullDeny()) return null;
if ($this->isFullDeny()) return false;
if ($this->isFullGrant()) return true;
@@ -96,8 +96,8 @@ class Utils_RecordBrowser_Access
// if there is any access granted - limit it based on restrict crits
if ($ret !== null && $ruleCrits['restrict'] instanceof Utils_RecordBrowser_Crits) $ret = Utils_RecordBrowserCommon::merge_crits($ret, $ruleCrits['restrict']);
return $ret;
return $ret?: false;
}
protected function getRecordInactiveAccess()
@@ -149,6 +149,8 @@ class Utils_RecordBrowser_Access
while ($row = $r->FetchRow())
$ruleCrits[$row['action']][$row['id']] = $this->parseAccessCrits($row['crits']);
$ruleCrits['selection'] = $ruleCrits['selection']?: $ruleCrits['view'];
self::$ruleCritsCache[$cache_key] = $ruleCrits;
}
@@ -173,6 +175,8 @@ class Utils_RecordBrowser_Access
'restrict' => null
];
foreach ( Utils_RecordBrowserCommon::get_custom_access_callbacks($this->tab) as $callback ) {
if (!is_callable($callback)) continue;
$callbackCrits = call_user_func($callback, $this->action, $this->record, $this->tab);
if (is_bool($callbackCrits)) {
@@ -244,14 +248,13 @@ class Utils_RecordBrowser_Access
foreach ( $grant_rule_ids as $rule_id )
$access_rule_blocked_fields[$rule_id] = $this->getRuleBlockedFields($rule_id);
Utils_RecordBrowserCommon::init($this->tab);
$fields = Utils_RecordBrowserCommon::init($this->tab);
$blocked_fields = count($access_rule_blocked_fields) > 1 ? call_user_func_array('array_intersect', $access_rule_blocked_fields): reset($access_rule_blocked_fields);
$full_field_access = array_fill_keys(array_keys(Utils_RecordBrowserCommon::$hash), true);
$full_field_access = array_fill_keys(array_column($fields, 'id'), true);
$blocked_field_access = [];
if ($blocked_fields) $blocked_field_access = array_fill_keys($blocked_fields, false);
$blocked_field_access = $blocked_fields? array_fill_keys($blocked_fields, false):[];
return array_merge($full_field_access, $blocked_field_access);
}
@@ -263,7 +266,7 @@ class Utils_RecordBrowser_Access
if (!isset(self::$ruleBlockedFieldsCache[$this->tab])) {
$r = DB::Execute('SELECT * FROM '.$this->tab.'_access_fields');
$fields = [];
$fields = array();
while ($row = $r->FetchRow()) {
$fields[$row['rule_id']][] = $row['block_field'];
}


+ 53
- 8
modules/Utils/RecordBrowser/Crits.php View File

@@ -385,12 +385,11 @@ class Utils_RecordBrowser_Crits extends Utils_RecordBrowser_CritsInterface

public function __construct($crits = null, $or = false)
{
if ($crits) {
if ($crits && !is_bool($crits)) {
if (is_array($crits)) {
$builder = new Utils_RecordBrowser_CritsBuilder();
$crits = $builder->build_single($crits);
$crits = Utils_RecordBrowser_CritsBuilder::create()->build_single($crits);
$this->component_crits = $crits;
} else {
} else {
$this->component_crits[] = $crits;
}
if (count($crits) > 1) {
@@ -511,15 +510,61 @@ class Utils_RecordBrowser_Crits extends Utils_RecordBrowser_CritsInterface
*/
public static function from_array($crits)
{
$builder = new Utils_RecordBrowser_CritsBuilder();
$ret = $builder->build_from_array($crits);
return $ret;
return Utils_RecordBrowser_CritsBuilder::create()->build_from_array($crits);
}
public static function merge($a = array(), $b = array(), $or = false)
{
if (is_array($a)) {
$a = self::from_array($a);
}
if (!($a instanceof self)) {
$a = new self($a);
}
if (is_array($b)) {
$b = self::from_array($b);
}
if (!($b instanceof self)) {
$b = new self($b);
}
if ($a->is_empty()) {
return clone $b;
}
if ($b->is_empty()) {
return clone $a;
}
$a = clone $a;
$b = clone $b;
return $or ? $a->_or($b) : $a->_and($b);
}
public static function and($crits, $_ = null)
{
$ret = [];
foreach (func_get_args() as $crits) {
$ret = self::merge($ret, $crits);
}
return $ret;
}
public static function or($crits, $_ = null)
{
$ret = [];
foreach (func_get_args() as $crits) {
$ret = self::merge($ret, $crits, true);
}
return $ret;
}
}

class Utils_RecordBrowser_CritsBuilder
{

public static function create() {
return new static();
}
public function build_single($crits)
{
$ret = array();


+ 9
- 2
modules/Utils/RecordBrowser/CritsToWords.php View File

@@ -81,8 +81,15 @@ class Utils_RecordBrowser_CritsToWords
protected function build_raw_sql_crits_to_words(Utils_RecordBrowser_CritsRawSQL $crits)
{
$sql = $crits->get_negation() ? $crits->get_negation_sql() : $crits->get_sql();
$value = implode(', ', $crits->get_vals());
$ret = __('Raw SQL') . ': ' . "'{$sql}'" . __('with values') . ': ' . "({$value})";
$vals = [];
foreach ($crits->get_vals() as $value) {
if (is_a($value, DateTime::class))
$value = $value->format('Y-m-d H:i:s');
$vals[] = $value;
}
$value = implode(', ', $vals);
$ret = __('Raw SQL') . ': ' . "'{$sql}' " . __('with values') . ': ' . "({$value})";
return array('str' => $ret, 'multiple' => true);
}



+ 36
- 28
modules/Utils/RecordBrowser/RecordBrowserCommon_0.php View File

@@ -98,7 +98,8 @@ class Utils_RecordBrowserCommon extends ModuleCommon {
if (!isset(self::$hash[$field])) trigger_error('Unknown field "'.$field.'" for recordset "'.$tab.'"',E_USER_ERROR);
$field = self::$hash[$field];
}
if ($desc===null) $desc = self::$table_rows[$field];
$desc = array_merge(self::$table_rows[$field], $desc?: []);
if(!array_key_exists('id',$record)) $record['id'] = null;
if (!array_key_exists($desc['id'],$record)) trigger_error($desc['id'].' - unknown field for record '.serialize($record), E_USER_ERROR);
$val = $record[$desc['id']];
@@ -650,6 +651,7 @@ class Utils_RecordBrowserCommon extends ModuleCommon {
'id'=>self::get_field_id($row['field']),
'pkey'=>$row['id'],
'type'=>$row['type'],
'caption'=>$row['caption'],
'visible'=>$row['visible'],
'required'=>($row['type']=='calculated'?false:$row['required']),
'extra'=>$row['extra'],
@@ -1385,7 +1387,7 @@ class Utils_RecordBrowserCommon extends ModuleCommon {
$values[$desc['id']] = self::encode_multi($filestorageIds[$field]);
}

if ($desc['type']=='calculated' && preg_match('/^[a-z]+(\([0-9]+\))?$/i',$desc['param'])===0) continue; // FIXME move DB definiton to *_field table
if (($desc['type']=='calculated' || $desc['type']=='hidden') && preg_match('/^[a-z]+(\([0-9]+\))?$/i',$desc['param'])===0) continue; // FIXME move DB definiton to *_field table
if (!isset($values[$desc['id']]) || $values[$desc['id']]==='') continue;
if (!is_array($values[$desc['id']])) $values[$desc['id']] = trim($values[$desc['id']]);
if ($desc['type']=='long text')
@@ -1560,28 +1562,7 @@ class Utils_RecordBrowserCommon extends ModuleCommon {
date('Y-m-d H:i:s')));
}
public static function merge_crits($a = array(), $b = array(), $or=false) {
if (is_array($a)) {
$a = Utils_RecordBrowser_Crits::from_array($a);
}
if (!($a instanceof Utils_RecordBrowser_Crits)) {
$a = new Utils_RecordBrowser_Crits($a);
}
if (is_array($b)) {
$b = Utils_RecordBrowser_Crits::from_array($b);
}
if (!($b instanceof Utils_RecordBrowser_Crits)) {
$b = new Utils_RecordBrowser_Crits($b);
}
if ($a->is_empty()) {
return clone $b;
}
if ($b->is_empty()) {
return clone $a;
}
$a = clone $a;
$b = clone $b;
$ret = $or ? $a->_or($b) : $a->_and($b);
return $ret;
return Utils_RecordBrowser_Crits::merge($a, $b, $or);
}
public static function build_query($tab, $crits = null, $admin = false, $order = array(), $tab_alias = 'r') {
static $stack = array();
@@ -1595,7 +1576,7 @@ class Utils_RecordBrowserCommon extends ModuleCommon {
}

$access_crits = ($admin || in_array($tab, $stack)) ? true : self::get_access_crits($tab, 'browse');
if ($access_crits == false) return array();
if ($access_crits === false) return array();
elseif ($access_crits !== true) {
$crits = self::merge_crits($crits, $access_crits);
}
@@ -3185,7 +3166,33 @@ class Utils_RecordBrowserCommon extends ModuleCommon {
}
return DB::GetOne('SELECT pattern FROM recordbrowser_clipboard_pattern WHERE tab=%s AND enabled=1', array($tab));
}
public static function replace_clipboard_pattern($text, $data) {
/* some complicate preg match to find every occurence
* of %{ .. {f_name} .. } pattern
*/
$match = [];
if (preg_match_all('/%\{(([^%\}\{]*?\{[^%\}\{]+?\}[^%\}\{]*?)+?)\}/', $text, $match)) { // match for all patterns %{...{..}...}
foreach ($match[0] as $k => $matched_string) {
$text_replace = $match[1][$k];
$changed = false;
$second_match = [];
while(preg_match('/\{(.+?)\}/', $text_replace, $second_match)) { // match for keys in braces {key}
$replace_value = '';
if(array_key_exists($second_match[1], $data)) {
$replace_value = $data[$second_match[1]];
$changed = true;
}
$text_replace = str_replace($second_match[0], $replace_value, $text_replace);
}
if(! $changed ) $text_replace = '';
$text = str_replace($matched_string, $text_replace, $text);
}
}
return $text;
}
public static function get_field_tooltip($label) {
if(strpos($label,'Utils_Tooltip')!==false) return $label;
$args = func_get_args();
@@ -3567,12 +3574,13 @@ class Utils_RecordBrowserCommon extends ModuleCommon {
if(!empty($fileStorageId)) {
$actions = $fileHandler->getActionUrlsRB($fileStorageId, $tab, $r['id'], $desc['id']);
$labels[]= Utils_FileStorageCommon::get_file_label($fileStorageId, $nolink, true, $actions);
$inline_nodes[]= Utils_FileStorageCommon::get_file_inline_node($fileStorageId, $actions);
if (!($desc['nopreview']?? false))
$inline_nodes[]= Utils_FileStorageCommon::get_file_inline_node($fileStorageId, $actions, $desc['max-width']?? '200px');
}
}
$inline_nodes = array_filter($inline_nodes);
return implode('<br>', $labels) . ($inline_nodes? '<hr>': '') . implode('<hr>', $inline_nodes);
return implode('<br>', $labels) . ($inline_nodes? '<hr>': '') . implode('&nbsp;', $inline_nodes);
}

public static function QFfield_file(&$form, $field, $label, $mode, $default, $desc, $rb_obj)


+ 13
- 35
modules/Utils/RecordBrowser/RecordBrowser_0.php View File

@@ -233,7 +233,7 @@ class Utils_RecordBrowser extends Module {
self::$rb_obj = $this;
$this->tab = & $this->get_module_variable('tab', $tab);
if ($this->tab!==null) Utils_RecordBrowserCommon::check_table_name($this->tab);
load_js('modules/Utils/RecordBrowser/main.js');
load_js($this->get_module_dir() . 'main.js');
}

public function init($admin=false, $force=false) {
@@ -1147,7 +1147,6 @@ class Utils_RecordBrowser extends Module {

if ($mode!='add') {
$theme -> assign('info_tooltip', '<a '.Utils_TooltipCommon::open_tag_attrs(Utils_RecordBrowserCommon::get_html_record_info($this->tab, $id)).'><img border="0" src="'.Base_ThemeCommon::get_template_file('Utils_RecordBrowser','info.png').'" /></a>');
$row_data= array();

if ($mode!='history') {
if ($this->favorites)
@@ -1161,34 +1160,13 @@ class Utils_RecordBrowser extends Module {
}
if ($this->clipboard_pattern) {
$theme -> assign('clipboard_tooltip', '<a '.Utils_TooltipCommon::open_tag_attrs(__('Click to export values to copy')).' '.Libs_LeightboxCommon::get_open_href('clipboard').'><img border="0" src="'.Base_ThemeCommon::get_template_file('Utils_RecordBrowser','clipboard.png').'" /></a>');
$text = $this->clipboard_pattern;
$record = Utils_RecordBrowserCommon::get_record($this->tab, $id);
/* for every field name store its value */
$data = array();
foreach($this->table_rows as $desc) {
$fval = Utils_RecordBrowserCommon::get_val($this->tab, $desc['id'], $record, true);
if(strlen($fval)) $data[$desc['id']] = $fval;
}
/* some complicate preg match to find every occurence
* of %{ .. {f_name} .. } pattern
*/
if (preg_match_all('/%\{(([^%\}\{]*?\{[^%\}\{]+?\}[^%\}\{]*?)+?)\}/', $text, $match)) { // match for all patterns %{...{..}...}
foreach ($match[0] as $k => $matched_string) {
$text_replace = $match[1][$k];
$changed = false;
while(preg_match('/\{(.+?)\}/', $text_replace, $second_match)) { // match for keys in braces {key}
$replace_value = '';
if(array_key_exists($second_match[1], $data)) {
$replace_value = $data[$second_match[1]];
$changed = true;
}
$text_replace = str_replace($second_match[0], $replace_value, $text_replace);
}
if(! $changed ) $text_replace = '';
$text = str_replace($matched_string, $text_replace, $text);
}
}
load_js("modules/Utils/RecordBrowser/selecttext.js");
$data = Utils_RecordBrowserCommon::get_record_vals($this->tab, $record, true, array_column($this->table_rows, 'id'));

$text = Utils_RecordBrowserCommon::replace_clipboard_pattern($this->clipboard_pattern, array_filter($data));
load_js($this->get_module_dir() . 'selecttext.js');
/* remove all php new lines, replace <br>|<br/> to new lines and quote all special chars */
$ftext = htmlspecialchars(preg_replace('#<[bB][rR]/?>#', "\n", str_replace("\n", '', $text)));
$flash_copy = '<object width="60" height="20">'.
@@ -2487,13 +2465,13 @@ class Utils_RecordBrowser extends Module {
);
} else {
if (!isset($field_hash[$k])) continue;
$new = $this->get_val($field_hash[$k], $created, false, $this->table_rows[$field_hash[$k]]);
if ($this->table_rows[$field_hash[$k]]['type'] == 'multiselect') $v = Utils_RecordBrowserCommon::decode_multi($v);
$created[$k] = $v;
$old = $this->get_val($field_hash[$k], $created, false, $this->table_rows[$field_hash[$k]]);
$gb_row = $gb_cha->get_new_row();
$gb_row->add_action('href="javascript:void(0);" onclick="recordbrowser_edit_history_jump(\''.$row['edited_on'].'\',\''.$this->tab.'\','.$created['id'].',\''.$form->get_name().'\');tabbed_browser_switch(1,2,null,\''.$tb_path.'\')"','View');
$gb_row->add_data(
$new = $this->get_val($field_hash[$k], $created, false, $this->table_rows[$field_hash[$k]]);
if ($this->table_rows[$field_hash[$k]]['type']=='multiselect') $v = Utils_RecordBrowserCommon::decode_multi($v);
$created[$k] = $v;
$old = $this->get_val($field_hash[$k], $created, false, $this->table_rows[$field_hash[$k]]);
$gb_row = $gb_cha->get_new_row();
$gb_row->add_action('href="javascript:void(0);" onclick="recordbrowser_edit_history_jump(\''.$row['edited_on'].'\',\''.$this->tab.'\','.$created['id'].',\''.$form->get_name().'\');tabbed_browser_switch(1,2,null,\''.$tb_path.'\')"','View');
$gb_row->add_data(
$date_and_time,
$row['edited_by']!==null?$user:'',
_V($this->table_rows[$field_hash[$k]]['name']), // TRSL


+ 5
- 0
modules/Utils/RecordBrowser/RecordPickerFS/RecordPickerFS_0.php View File

@@ -42,6 +42,11 @@ class Utils_RecordBrowser_RecordPickerFS extends Module {
public function show($tab, $crits=array(), $cols=array(), $order=array(), $filters=array(),$filters_defaults=array(),$path=null,$caption=null) {
$this->caption = $caption;
$rb = $this->init_module(Utils_RecordBrowser::module_name(), $tab, $tab.'_picker');
foreach ($filters as $field => $filter) {
if (!is_array($filter)) continue;
$rb->set_custom_filter($field, $filter);
}
if($filters_defaults) $rb->set_filters_defaults($filters_defaults);
// $rb->adv_search = true;
$rb->disable_actions();


+ 125
- 0
modules/Utils/Tray/Box.php View File

@@ -0,0 +1,125 @@
<?php

/*
* @author Georgi Hristov <ghristov@gmx.de>
* @copyright Copyright &copy; 2019, Georgi Hristov
* @license MIT
* @version 2.0
* @package epesi-tray
*
*/

defined("_VALID_ACCESS") || die('Direct access forbidden');

class Utils_Tray_Box {
protected $title;
protected $weight = 5;
/**
* @var Utils_Tray_Slot[]
*/
protected $slots = [];
protected $slotsLimit = false;
protected $limitSlotsDisabled = false;
protected $ignoreLimit = false;
public function __construct($settings) {
$this->setPropertiesFromArray($settings);
}
public function setPropertiesFromArray($settings) {
if (!is_array($settings)) return;
foreach ($settings as $key=>$value) {
$key = str_ireplace(' ', '', ucwords(str_ireplace('_', ' ', trim($key, '__'))));
if (is_callable([$this, 'set' . $key]))
call_user_func([$this, 'set' . $key], $value);
}
}

public function getTitle() {
return $this->title;
}

public function setTitle($title) {
$this->title = $title;
return $this;
}

public function getWeight() {
return $this->weight;
}

public function setWeight($weight) {
$this->weight = $weight;
return $this;
}

/**
* @return Utils_Tray_Slot[]
*/
public function getSlots() {
if (!$this->getSlotsLimit()) return $this->slots;
$ret = [];
$count = 0;
foreach ($this->slots as $slot) {
if ($count >= $this->getSlotsLimit() && !$slot->getIgnoreLimit()) continue;
$count++;
$ret[] = $slot;
}
return $ret;
}

public function setSlots($module, $tab, $slotDefinitions, $transCallbacks= []) {
foreach ($slotDefinitions as $definition) {
$slotModule = $definition['__module__']?? $module;
if ($slotFunction = $definition['__func__']?? null) {
$slotModule = [$slotModule, $slotFunction];
}
$slot = new Utils_Tray_Slot($this, $slotModule, $tab, $definition, $transCallbacks);
$this->slots[] = $slot;
}
uasort($this->slots, function (Utils_Tray_Slot $a, Utils_Tray_Slot $b) {
return $a->getWeight() - $b->getWeight();
});

return $this;
}
public function getSlotsLimit() {
if ($this->limitSlotsDisabled) return false;
return $this->slotsLimit;
}

public function setSlotsLimit($slotsLimit) {
$this->slotsLimit = is_numeric($slotsLimit)? $slotsLimit: false;
return $this;
}

public function getLimitSlotsDisabled() {
return $this->limitSlotsDisabled;
}

public function setLimitSlotsDisabled($limitSlotsDisabled) {
$this->limitSlotsDisabled = $limitSlotsDisabled;
return $this;
}

public function getIgnoreLimit() {
return $this->ignoreLimit;
}

public function setIgnoreLimit($ignoreLimit) {
$this->ignoreLimit = $ignoreLimit;
return $this;
}
}

?>

+ 248
- 0
modules/Utils/Tray/Slot.php View File

@@ -0,0 +1,248 @@
<?php

/*
* @author Georgi Hristov <ghristov@gmx.de>
* @copyright Copyright &copy; 2019, Georgi Hristov
* @license MIT
* @version 2.0
* @package epesi-tray
*
*/

defined("_VALID_ACCESS") || die('Direct access forbidden');

class Utils_Tray_Slot {
/**
* @var Utils_Tray_Box
*/
protected $box;
protected $module;
protected $function = null;
protected $tab;
protected $id;
protected $name;
protected $weight = 5;
protected $filters = [];
protected $transCallbacks = [];
protected $crits = null;
protected $count = null;
protected $visible = true;
protected $ignoreLimit = false;
public function __construct(Utils_Tray_Box $box, $module, $tab, $settings, $transCallbacks=[]) {
$this->setBox($box);
$this->setModule($module);
$this->setTab($tab);
$this->setTransCallbacks($transCallbacks);
$this->setPropertiesFromArray($settings);
}
public function setPropertiesFromArray($settings) {
if (!is_array($settings)) return;
foreach ($settings as $key=>$value) {
$key = str_ireplace(' ', '', ucwords(str_ireplace('_', ' ', trim($key, '__'))));
if (is_callable([$this, 'set' . $key]))
call_user_func([$this, 'set' . $key], $value);
}
}
public function isVisible($hideEmpty = true) {
return $this->visible && (!$this->isEmpty() || !$hideEmpty);
}
public function isEmpty() {
return empty($this->getCount());
}
public function getCount() {
return $this->count?? $this->count = Utils_RecordBrowserCommon::get_records_count($this->getTab(), $this->getCrits());
}

public function getBox() {
return $this->box;
}

public function setBox($box) {
$this->box = $box;
return $this;
}

public function getId() {
return $this->id;
}

public function setId($id) {
$this->id = Utils_RecordBrowserCommon::get_field_id($id);;
return $this;
}

public function getName() {
return $this->name;
}

public function setName($name) {
$this->setId($this->name = $name);
return $this;
}

public function getWeight() {
return $this->weight;
}

public function setWeight($weight) {
$this->weight = $weight;
return $this;
}

public function getFilters() {
return $this->filters;
}

public function setFilters($filters) {
$this->filters = $filters;
return $this;
}

public function getCrits() {
if ($this->crits) {
return is_callable($this->crits)? call_user_func($this->crits): $this->crits;
}
$this->count = null;
$crits = [];
foreach ($this->getFilters() as $field=>$val) {
$trans_callback = $this->getTransCallback($field);

$record_crits = is_callable($trans_callback)? call_user_func($trans_callback, $val, $field, $this->getTab()): [$field=>$val];

$crits = Utils_RecordBrowserCommon::merge_crits($crits, $record_crits);
}
// foreach ($crits as $k=>$c) if ($c==='__PERSPECTIVE__') {
// $crits[$k] = explode(',',trim(CRM_FiltersCommon::get(),'()'));
// if (isset($crits[$k][0]) && $crits[$k][0]=='') unset($crits[$k]);
// }

return $this->crits = $crits;
}

public function setCrits($crits) {
$this->crits = $crits;
return $this;
}
public function getTransCallback($field) {
return $this->transCallbacks[$field]?? [];
}

public function getTransCallbacks() {
return $this->transCallbacks;
}

public function setTransCallbacks($transCallbacks) {
$this->transCallbacks = $transCallbacks;
return $this;
}
public function getHtml() {
$tray_count_width = ($this->getCount() > 99)? 'style="width:28px;"':'';
return '<a '.Base_BoxCommon::create_href(null, $this->getModule(), $this->getFunction(), [$this->getTab()], null, ['tray_slot'=>$this->getId()]).'><div class="Utils_Tray__slot">'.
Utils_TooltipCommon::create('<img src="'.$this->getIcon().'">
<div class="Utils_Tray__slot_count" ' .$tray_count_width . '>' . $this->getCount().'</div><div class="Utils_Tray__slot_name">'._V($this->getName()).'</div>', $this->getTip()).'</div></a>';
}
public function getTip() {
return __('Click to view %s items from %s<br><br>%d item(s)',[_V($this->getName()),_V($this->getBox()->getTitle()), $this->getCount()]);
}
public function getIcon() {
$limits = [10=>'pile3', 5=>'pile2', 1=>'pile1', 0=>'pile0'];
foreach ($limits as $limit=>$file) {
if ($this->getCount() >= $limit) break;
}
return Base_ThemeCommon::get_template_file('Utils_Tray', $file.'.png');
}

public function getModule() {
return $this->module;
}

public function setModule($module) {
if (is_array($module)) {
$this->setFunction($module[1]);
$module = $module[0];
}
$this->module = $module;
return $this;
}
public function getFunction() {
return $this->function;
}

public function setFunction($function) {
$this->function = $function;
return $this;
}

public function getTab() {
return $this->tab;
}

public function setTab($tab) {
$this->tab = $tab;
return $this;
}
public function filtersExist() {
return $this->filters? true: false;
}
public function filtersMatch() {
if (!$this->filtersExist()) return false;
$filtered = false;
foreach ($_REQUEST as $id=>$value) {
if (stripos($id, 'filter__')!==false) {
$filtered = true;
break;
}
}
if (!$filtered) return false;
$filter_changed = false;
foreach ($this->getFilters() as $id=>$value) {
if ($_REQUEST['filter__'.$id]!= $value) {
$filter_changed=true;
break;
}
}
return !$filter_changed;
}

public function getIgnoreLimit() {
return $this->ignoreLimit;
}
public function setIgnoreLimit($ignoreLimit) {
$this->ignoreLimit = $ignoreLimit;
return $this;
}
public function setVisible($visible) {
$this->visible = $visible;
return $this;
}
}

?>

+ 87
- 116
modules/Utils/Tray/TrayCommon_0.php View File

@@ -1,11 +1,12 @@
<?php
/**
* @author Georgi Hristov <ghristov@gmx.de>
* @copyright Copyright &copy; 2014, Xoff Software GmbH
* @license MIT
* @version 1.0
* @package epesi-tray
*/
/*
* @author Georgi Hristov <ghristov@gmx.de>
* @copyright Copyright &copy; 2019, Georgi Hristov
* @license MIT
* @version 2.0
* @package epesi-tray
*/

defined("_VALID_ACCESS") || die('Direct access forbidden');

class Utils_TrayCommon extends ModuleCommon {
@@ -14,10 +15,12 @@ class Utils_TrayCommon extends ModuleCommon {
private static $tmp_trays;

public static function menu() {
if(Base_AclCommon::check_permission('Dashboard')) {
$tray_settings = Utils_TrayCommon::get_trays();
if (count($tray_settings)>0)
return array(_M('Tray')=>array('__icon__'=>'pile_small.png'));
if (Base_AclCommon::check_permission('Dashboard')) {
if (ModuleManager::check_common_methods('tray')) return array(
_M('Tray') => array(
'__icon__' => 'icon.png'
)
);
}
return array();
}
@@ -36,15 +39,62 @@ class Utils_TrayCommon extends ModuleCommon {

public static function applet_settings() {
return array(
array(
'name'=>'title','label'=>__('Title'),'type'=>'text','default'=>__('Tray'),
'rule'=>array(array('message'=>__('Field required'), 'type'=>'required'))),
array(
'name'=>'max_trays','label'=>__('Tray Limit'),'type'=>'select','values'=>array('__NULL__'=>__('No Limit'),4=>4,6=>6,8=>8,10=>10),'default'=>6),
array(
'name'=>'max_slots','label'=>__('Slots Limit'),'type'=>'select','values'=>array('__NULL__'=>__('No Limit'),2=>2,3=>3,4=>4,5=>5,6=>6),'default'=>3),
array(
'name' => 'title',
'label' => __('Title'),
'type' => 'text',
'default' => __('Tray'),
'rule' => array(
array(
'message' => __('Field required'),
'type' => 'required'
)
)
),
array(
'name' => 'max_trays',
'label' => __('Tray Limit'),
'type' => 'select',
'values' => array(
'__NULL__' => __('No Limit'),
4 => 4,
6 => 6,
8 => 8,
10 => 10
),
'default' => 6
),
array(
'name' => 'max_slots',
'label' => __('Slots Limit'),
'type' => 'select',
'values' => array(
'__NULL__' => __('No Limit'),
2 => 2,
3 => 3,
4 => 4,
5 => 5,
6 => 6,
10 => 10
),
'default' => 5
),
array(
'name' => 'hide_empty_slots',
'label' => __('Hide Empty Slots'),
'type' => 'checkbox',
'default' => 1
)
);
}
public static function enable($tab) {
Utils_RecordBrowserCommon::new_browse_mode_details_callback($tab, 'Utils_Tray', 'tray_tab_browse_details');
}
public static function disable($tab) {
Utils_RecordBrowserCommon::delete_browse_mode_details_callback($tab, 'Utils_Tray', 'tray_tab_browse_details');
}

public static function get_trays() {
static $trays;
@@ -56,104 +106,25 @@ class Utils_TrayCommon extends ModuleCommon {
return $trays;
}

public static function are_filters_changed($tray_slot_filters) {
$filtered = false;
foreach ($_REQUEST as $id=>$value) {
if (stripos($id, 'filter__')!==false) {
$filtered = true;
break;
}
}

if (!$filtered) return false;

$filter_changed = false;
foreach ($tray_slot_filters as $id=>$value) {
if ($_REQUEST['filter__'.$id]!= $value) {
$filter_changed=true;
break;
}
}

return $filter_changed;
}

public static function get_tray($tab, $tray_settings) {
if (!isset($tab) || !isset($tray_settings['__title__']) || !isset($tray_settings['__slots__'])) return array();

$slot_defs = self::get_slots($tab, $tray_settings);
$weight = isset($tray_settings['__weight__'])? $tray_settings['__weight__']: 0;

return array('__title__'=>$tray_settings['__title__'], '__weight__'=> $weight, '__slots__'=>$slot_defs);
}

public static function get_slots($tab, $tray_settings) {
if (!isset($tray_settings['__title__']) || !isset($tray_settings['__slots__'])) return array();

$ret = array();
foreach ($tray_settings['__slots__'] as $slot) {
if (!isset($slot['__name__'])) continue;

$crits = self::get_slot_crits($slot, $tray_settings);
$slot_id = Utils_RecordBrowserCommon::get_field_id($slot['__name__']);

$ret[$slot_id] = $slot + array('__id__'=>$slot_id, '__count__'=>Utils_RecordBrowserCommon::get_records_count($tab, $crits) ,'__crits__'=>$crits);
}

return $ret;
}

public static function get_slot_crits($slot, $tray_settings) {
$crits = array();
$trans_callbacks = isset($tray_settings['__trans_callbacks__'])? $tray_settings['__trans_callbacks__']:null;

$record_filters = isset($slot['__filters__']) ? $slot['__filters__'] : array();

foreach ($record_filters as $field=>$val) {
$trans_callback = null;
if (isset($trans_callbacks[$field])) {
$trans_callback = is_array($trans_callbacks[$field])? implode('::', $trans_callbacks[$field]): $trans_callbacks[$field];
}

$record_crits = is_callable($trans_callback)? call_user_func($trans_callback, $val, $field): array($field=>$val);

$crits += is_array($record_crits)? $record_crits: array();
}

foreach ($crits as $k=>$c) if ($c==='__PERSPECTIVE__') {
$crits[$k] = explode(',',trim(CRM_FiltersCommon::get(),'()'));
if (isset($crits[$k][0]) && $crits[$k][0]=='') unset($crits[$k]);
}

return $crits;
}

public static function sort_tray_cmp($a, $b) {
$aw = isset(self::$tmp_trays[$a]['__weight__']) ? self::$tmp_trays[$a]['__weight__']:0;
$bw = isset(self::$tmp_trays[$b]['__weight__']) ? self::$tmp_trays[$b]['__weight__']:0;
if(!isset($aw) || !is_numeric($aw)) $aw=0;
if(!isset($bw) || !is_numeric($bw)) $bw=0;
if($aw==$bw)
return strcasecmp($a, $b);
return $aw-$bw;
}

public static function sort_trays(& $trays, $include_slots = true) {
self::$tmp_trays = $trays;
uksort($trays, array('Utils_TrayCommon','sort_tray_cmp'));

foreach($trays as &$t) {
if(is_array($t) && array_key_exists('__slots__',$t) && $include_slots) {
self::sort_trays($t['__slots__']);
}
}
unset($trays['__weight__']);
}

public static function user_settings(){
return array(__('Tray settings')=>array(
array('name'=>'tray_cols','label'=>__('Tray Columns'),'type'=>'select','values'=>Utils_TrayCommon::$tray_cols,'default'=>3),
array('name'=>'tray_layout','label'=>__('Tray Layout'),'type'=>'select','values'=>Utils_TrayCommon::$tray_layout,'default'=>'checkered')));
public static function user_settings() {
return array(
__('Tray settings') => array(
array(
'name' => 'tray_cols',
'label' => __('Tray Columns'),
'type' => 'select',
'values' => Utils_TrayCommon::$tray_cols,
'default' => 3
),
array(
'name' => 'tray_layout',
'label' => __('Tray Layout'),
'type' => 'select',
'values' => Utils_TrayCommon::$tray_layout,
'default' => 'checkered'
)
)
);
}

//////////////////////////


+ 16
- 14
modules/Utils/Tray/TrayInstall.php View File

@@ -1,15 +1,16 @@
<?php
/**
* @author Georgi Hristov <ghristov@gmx.de>
* @copyright Copyright &copy; 2014, Xoff Software GmbH
* @license MIT
* @version 1.0
* @package epesi-tray
*/
/*
* @author Georgi Hristov <ghristov@gmx.de>
* @copyright Copyright &copy; 2019, Georgi Hristov
* @license MIT
* @version 2.0
* @package epesi-tray
*
*/
defined("_VALID_ACCESS") || die('Direct access forbidden');

class Utils_TrayInstall extends ModuleInstall {
const version = '1.0';
const version = '2.0';

public function install() {
Base_ThemeCommon::install_default_theme($this->get_type());
@@ -30,15 +31,16 @@ class Utils_TrayInstall extends ModuleInstall {
array('name'=>Utils_RecordBrowserInstall::module_name(), 'version'=>0),
array('name'=>Base_LangInstall::module_name(), 'version'=>0),
array('name'=>Base_User_SettingsInstall::module_name(),'version'=>0),
array('name'=>Base_ThemeInstall::module_name(),'version'=>0));
array('name'=>Base_ThemeInstall::module_name(),'version'=>0)
);
}
public static function info() {
$html="Displays overview pending items in recordsets";
return array(
'Description'=>$html,
'Author'=>'<a href="mailto:ghristov@gmx.de">Georgi Hristov</a>',
'License'=>'MIT');
'Description' => "Displays overview pending items in recordsets",
'Author' => '<a href="mailto:ghristov@gmx.de">Georgi Hristov</a>',
'License' => 'MIT'
);
}
public function simple_setup() {


+ 192
- 172
modules/Utils/Tray/Tray_0.php View File

@@ -10,14 +10,19 @@
* - __title__ setting determines the tray that slots will be included in.
* Make sure the title is defined using _M('title') function.
*
* - __slots__ setting defines the slot settings: __name__, __filters__, __weight__
* - __slots__ setting defines the slot settings: __name__, __filters__, __crits__, __weight__, __module__
* = __name__ setting determines the tray display title.
* Make sure the name is defined using _M('name') function
* = __weight__ setting defines the order in which the slots are displayed
* = __filters__ setting defines the default filter values that represent the records contained in each slot
* = __crits__ setting is crits or a callback that defines crits for the slot
* = __module__ setting defines the module to jump to, default is the module where tray method was called from
* = __ignore_limit__ - boolean - the slot is displayed irrelevant of slot limit set - can be used for important slots
*
* - __weight__ setting defines the order in which the trays are displayed
*
* - __ignore_limit__ - boolean - the box is displayed irrelevant of box limit set - can be used for important boxes
*
* - __trans_callbacks__ setting is the list of callback functions for each field that has custom filter defined
*