403Webshell
Server IP : 80.87.202.40  /  Your IP : 216.73.216.169
Web Server : Apache
System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64
User : bitrix ( 600)
PHP Version : 8.2.27
Disable Function : NONE
MySQL : OFF |  cURL : ON |  WGET : ON |  Perl : ON |  Python : OFF |  Sudo : ON |  Pkexec : ON
Directory :  /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/workflow/classes/general/

Upload File :
current_dir [ Writeable] document_root [ Writeable]

 

Command :


[ Back ]     

Current File : /home/bitrix/ext_www/rospirotorg.ru/bitrix/modules/workflow/classes/general/workflow.php
<?php

IncludeModuleLangFile(__FILE__);

class CWorkflow
{
	public static function OnPanelCreate()
	{
		global $APPLICATION, $USER;
		$cur_page_param = $APPLICATION->GetCurPageParam();
		$cur_page = $APPLICATION->GetCurPage(true);
		$cur_dir = $APPLICATION->GetCurDir();

		// New page
		$flow_link_new = self::GetEditLink([SITE_ID, rtrim(GetDirPath($cur_page), '/') . '/untitled.php'], $status_id, $status_title, 'standart.php', LANGUAGE_ID, $cur_page_param);
		$create_permission = $flow_link_new <> '' && $USER->CanDoFileOperation('fm_edit_in_workflow', [SITE_ID, $cur_dir]);
		// Document history
		$flow_link_hist = '/bitrix/admin/workflow_history_list.php?lang=' . LANGUAGE_ID . '&find_filename=' . urlencode($cur_page) . '&find_filename_exact_match=Y&set_filter=Y';
		$history_permission = $USER->CanDoFileOperation('fm_edit_in_workflow', [SITE_ID, $cur_page]);
		// Current page
		$flow_link_edit = self::GetEditLink([SITE_ID, $cur_page], $status_id, $status_title, '', LANGUAGE_ID, $cur_page_param);
		$edit_permission = $flow_link_edit <> '' && $history_permission;

		//Big button
		if ($edit_permission)
		{
			$public_edit = $APPLICATION->GetPopupLink([
				'URL' => $flow_link_edit . '&bxpublic=Y&from_module=workflow',
				'PARAMS' => [
					'min_width' => 700,
					'min_height' => 400,
					'height' => 700,
					'width' => 400,
				],
			]);

			$APPLICATION->AddPanelButton([
				'HREF' => 'javascript:' . $public_edit,
				'TYPE' => 'BIG',
				'ID' => 'edit',
				'ICON' => 'bx-panel-edit-page-icon',
				'ALT' => GetMessage('top_panel_edit_title'),
				'TEXT' => GetMessage('top_panel_edit_new'),
				'MAIN_SORT' => '200',
				'SORT' => 10,
				'MENU' => [],
				'HK_ID' => 'top_panel_edit_new',
				'RESORT_MENU' => true,
				'HINT' => [
					'TITLE' => GetMessage('top_panel_edit_new_tooltip_title'),
					'TEXT' => GetMessage('top_panel_edit_new_tooltip'),
				],
			]);
		}

		// New page
		if ($create_permission)
		{
			$APPLICATION->AddPanelButtonMenu('create', ['SEPARATOR' => true, 'SORT' => 49]);
			$APPLICATION->AddPanelButtonMenu('create', [
				'SRC' => '/bitrix/images/workflow/new_page.gif',
				'TEXT' => GetMessage('FLOW_PANEL_CREATE_WITH_WF'),
				'TITLE' => GetMessage('FLOW_PANEL_CREATE_ALT'),
				'ACTION' => "jsUtils.Redirect([], '" . CUtil::JSEscape($flow_link_new) . "')",
				'HK_ID' => 'FLOW_PANEL_CREATE_WITH_WF',
				'SORT' => 50,
			]);
		}

		if ($edit_permission || $history_permission)
		{
			$APPLICATION->AddPanelButtonMenu('edit', ['SEPARATOR' => true, 'SORT' => 79]);
		}

		// Current page
		if ($edit_permission)
		{
			$APPLICATION->AddPanelButtonMenu('edit', [
				'SRC' => '/bitrix/images/workflow/edit_flow_public.gif',
				'TEXT' => GetMessage('FLOW_PANEL_EDIT_WITH_WF'),
				'TITLE' => (intval($status_id) > 0 ? GetMessage('FLOW_CURRENT_STATUS') . ' [' . $status_id . '] ' . $status_title : GetMessage('FLOW_PANEL_EDIT_ALT')),
				'ACTION' => "jsUtils.Redirect([], '" . CUtil::JSEscape($flow_link_edit) . "')",
				'HK_ID' => 'FLOW_PANEL_EDIT_WITH_WF',
				'SORT' => 80,
			]);
		}

		// Document history
		if ($history_permission)
		{
			$flow_link_hist = '/bitrix/admin/workflow_history_list.php?lang=' . LANGUAGE_ID . '&find_filename=' . urlencode($cur_page) . '&find_filename_exact_match=Y&set_filter=Y';
			$APPLICATION->AddPanelButtonMenu('edit', [
				'SRC' => '/bitrix/images/workflow/history.gif',
				'TEXT' => GetMessage('FLOW_PANEL_HISTORY'),
				'TITLE' => GetMessage('FLOW_PANEL_HISTORY_ALT'),
				'ACTION' => "jsUtils.Redirect([], '" . CUtil::JSEscape($flow_link_hist) . "')",
				'HK_ID' => 'FLOW_PANEL_HISTORY',
				'SORT' => 81,
			]);
		}
	}

	public static function OnChangeFile($path, $site)
	{
		global $BX_WORKFLOW_PUBLISHED_PATH, $BX_WORKFLOW_PUBLISHED_SITE;
		if ($BX_WORKFLOW_PUBLISHED_PATH == $path && $BX_WORKFLOW_PUBLISHED_SITE == $site)
		{
			return;
		}

		global $DB, $USER, $APPLICATION;
		$HISTORY_SIMPLE_EDITING = COption::GetOptionString('workflow', 'HISTORY_SIMPLE_EDITING', 'N');
		if ($HISTORY_SIMPLE_EDITING == 'Y')
		{
			$HISTORY_COPIES = intval(COption::GetOptionString('workflow', 'HISTORY_COPIES', '10'));
			self::CleanUpHistoryCopies_SE($path, $HISTORY_COPIES - 1);
			if ($HISTORY_COPIES > 0)
			{
				$DOC_ROOT = \Bitrix\Main\SiteTable::getDocumentRoot($site === false ? null : $site);
				$filesrc = $APPLICATION->GetFileContent($DOC_ROOT . $path);
				$arContent = ParseFileContent($filesrc);
				$TITLE = $arContent['TITLE'];
				$BODY = $arContent['CONTENT'];
				$arFields = [
					'DOCUMENT_ID' => 0,
					'MODIFIED_BY' => $USER ? $USER->GetID() : 1,
					'TITLE' => $TITLE,
					'FILENAME' => $path,
					'SITE_ID' => $site,
					'BODY' => $BODY,
					'BODY_TYPE' => 'html',
					'STATUS_ID' => 1,
					'~TIMESTAMP_X' => $DB->CurrentTimeFunction(),
				];
				$DB->Add('b_workflow_log', $arFields, ['BODY'], 'workflow');
			}
		}
	}

	public static function SetHistory($DOCUMENT_ID)
	{
		global $DB;

		$LOG_ID = false;
		$DOCUMENT_ID = intval($DOCUMENT_ID);
		$HISTORY_COPIES = intval(COption::GetOptionString('workflow', 'HISTORY_COPIES', '10'));
		$z = self::GetByID($DOCUMENT_ID);
		if ($zr = $z->Fetch())
		{
			self::CleanUpHistoryCopies($DOCUMENT_ID, $HISTORY_COPIES - 1);
			if ($HISTORY_COPIES > 0)
			{
				$arFields = [
					'DOCUMENT_ID' => $DOCUMENT_ID,
					'MODIFIED_BY' => $zr['MODIFIED_BY'],
					'TITLE' => $zr['TITLE'],
					'FILENAME' => $zr['FILENAME'],
					'SITE_ID' => $zr['SITE_ID'],
					'BODY' => $zr['BODY'],
					'BODY_TYPE' => $zr['BODY_TYPE'],
					'STATUS_ID' => $zr['STATUS_ID'],
					'COMMENTS' => $zr['COMMENTS'],
					'~TIMESTAMP_X' => $DB->CurrentTimeFunction(),
				];
				$LOG_ID = $DB->Add('b_workflow_log', $arFields, ['BODY'], 'workflow');
			}
		}

		return $LOG_ID;
	}

	// Deletes old copies from document's history
	public static function CleanUpHistoryCopies($DOCUMENT_ID=false, $HISTORY_COPIES=false)
	{
		global $DB;

		if ($HISTORY_COPIES === false)
		{
			$HISTORY_COPIES = intval(COption::GetOptionString('workflow', 'HISTORY_COPIES', '10'));
		}

		$DOCUMENT_ID = intval($DOCUMENT_ID);
		if ($DOCUMENT_ID > 0)
		{
			$strSqlSearch = ' and ID = ' . $DOCUMENT_ID . ' ';
		}
		else
		{
			$strSqlSearch = '';
		}

		$strSql = 'SELECT ID FROM b_workflow_document WHERE 1=1 ' . $strSqlSearch;
		$z = $DB->Query($strSql);
		while ($zr = $z->Fetch())
		{
			$DID = $zr['ID'];
			$strSql = '
				SELECT
					ID
				FROM
					b_workflow_log
				WHERE
					DOCUMENT_ID = ' . $DID . '
				ORDER BY
					ID desc
				';
			$t = $DB->Query($strSql);
			$i = 0;
			$str_id = '0';
			while ($tr = $t->Fetch())
			{
				$i++;
				if ($i > $HISTORY_COPIES)
				{
					$str_id .= ', ' . $tr['ID'];
				}
			}
			$strSql = 'DELETE FROM b_workflow_log WHERE ID in (' . $str_id . ')';
			$DB->Query($strSql);
		}
	}

	// Deletes old copies from document's history (simple edit - SE)
	public static function CleanUpHistoryCopies_SE($FILENAME, $HISTORY_COPIES=false)
	{
		global $DB;

		if ($HISTORY_COPIES === false)
		{
			$HISTORY_COPIES = intval(COption::GetOptionString('workflow', 'HISTORY_COPIES', '10'));
		}
		$strSql = "
			SELECT
				ID
			FROM
				b_workflow_log
			WHERE
				FILENAME = '" . $DB->ForSql($FILENAME, 255) . "'
			and DOCUMENT_ID = 0
			ORDER BY
				ID desc
			";
		$t = $DB->Query($strSql);
		$i = 0;
		$str_id = '0';
		while ($tr = $t->Fetch())
		{
			$i++;
			if ($i > $HISTORY_COPIES)
			{
				$str_id .= ', ' . $tr['ID'];
			}
		}
		$strSql = 'DELETE FROM b_workflow_log WHERE ID in (' . $str_id . ')';
		$DB->Query($strSql);
	}

	// saves changes history and send e-mails on status change
	public static function SetMove($DOCUMENT_ID, $STATUS_ID, $OLD_STATUS_ID, $LOG_ID)
	{
		global $DB, $USER, $APPLICATION;

		$DOCUMENT_ID = intval($DOCUMENT_ID);
		$STATUS_ID = intval($STATUS_ID);
		$OLD_STATUS_ID = intval($OLD_STATUS_ID);
		$LOG_ID = intval($LOG_ID);

		$arFields = [
			'TIMESTAMP_X' => $DB->GetNowFunction(),
			'DOCUMENT_ID' => $DOCUMENT_ID,
			'OLD_STATUS_ID' => $OLD_STATUS_ID,
			'STATUS_ID' => $STATUS_ID,
			'LOG_ID' => $LOG_ID,
			'USER_ID' => intval($USER->GetID()),
		];
		$DB->Insert('b_workflow_move', $arFields);

		if ($STATUS_ID != $OLD_STATUS_ID)
		{
			CTimeZone::Disable();
			$d = self::GetByID($DOCUMENT_ID);
			CTimeZone::Enable();

			if ($dr = $d->Fetch())
			{
				$STATUS_ID = $dr['STATUS_ID'];

				// gather email of the workflow admins
				$WORKFLOW_ADMIN_GROUP_ID = COption::GetOptionInt('workflow', 'WORKFLOW_ADMIN_GROUP_ID', 0);
				$strSql = '
					SELECT
						U.ID,
						U.EMAIL
					FROM
						b_user U,
						b_user_group UG
					WHERE
						UG.GROUP_ID = ' . $WORKFLOW_ADMIN_GROUP_ID . "
						and U.ID = UG.USER_ID
						and U.ACTIVE = 'Y'
				";
				$a = $DB->Query($strSql);
				$arAdmin = [];
				while ($ar = $a->Fetch())
				{
					$arAdmin[$ar['ID']] = $ar['EMAIL'];
				}

				// gather email for BCC
				$arBCC = [];

				// gather all who changed doc in its current status
				$strSql = '
					SELECT
						USER_ID
					FROM
						b_workflow_move
					WHERE
						DOCUMENT_ID = ' . $DOCUMENT_ID . '
						and OLD_STATUS_ID = ' . $STATUS_ID . '
				';
				$z = $DB->Query($strSql);
				while ($zr = $z->Fetch())
				{
					$arBCC[$zr['EMAIL']] = $zr['EMAIL'];
				}

				// gather all editors
				// in case status have notifier flag
				$strSql = '
					SELECT DISTINCT
						UG.USER_ID
						,U.EMAIL
					FROM
						b_workflow_status S,
						b_workflow_status2group SG,
						b_user U,
						b_user_group UG
					WHERE
						S.ID = ' . $STATUS_ID . "
						and S.NOTIFY = 'Y'
						and SG.STATUS_ID = S.ID
						and SG.PERMISSION_TYPE = '2'
						and UG.GROUP_ID = SG.GROUP_ID
						and U.ID = UG.USER_ID
						and U.ACTIVE = 'Y'
				";
				$z = $DB->Query($strSql);
				while ($zr = $z->Fetch())
				{
					if (!array_key_exists($zr['EMAIL'], $arBCC))
					{
						$grp = [];
						$rs = $USER->GetUserGroupList($zr['USER_ID']);
						while ($ar = $rs->Fetch())
						{
							$grp[] = $ar['GROUP_ID'];
						}

						$arTasks = $APPLICATION->GetFileAccessPermission($dr['FILENAME'], $grp, true);
						foreach ($arTasks as $task_id)
						{
							$arOps = CTask::GetOperations($task_id, true);
							if (in_array('fm_edit_in_workflow', $arOps))
							{
								$arBCC[$zr['EMAIL']] = $zr['EMAIL'];

								break;
							}
						}
					}
				}

				unset($arBCC[$dr['EUSER_EMAIL']]);

				if (array_key_exists($dr['ENTERED_BY'], $arAdmin))
				{
					$dr['EUSER_NAME'] .= ' (Admin)';
				}

				// it is not new doc
				if ($OLD_STATUS_ID > 0)
				{
					if (array_key_exists($dr['MODIFIED_BY'], $arAdmin))
					{
						$dr['MUSER_NAME'] .= ' (Admin)';
					}
					$q = CWorkflowStatus::GetByID($OLD_STATUS_ID);
					$qr = $q->Fetch();
					// send change notification
					$arEventFields = [
						'ID' => $dr['ID'],
						'ADMIN_EMAIL' => implode(',', $arAdmin),
						'BCC' => implode(',', $arBCC),
						'PREV_STATUS_ID' => $OLD_STATUS_ID,
						'PREV_STATUS_TITLE' => $qr['TITLE'],
						'STATUS_ID' => $dr['STATUS_ID'],
						'STATUS_TITLE' => $dr['STATUS_TITLE'],
						'DATE_ENTER' => $dr['DATE_ENTER'],
						'ENTERED_BY_ID' => $dr['ENTERED_BY'],
						'ENTERED_BY_NAME' => $dr['EUSER_NAME'],
						'ENTERED_BY_EMAIL' => $dr['EUSER_EMAIL'],
						'DATE_MODIFY' => $dr['DATE_MODIFY'],
						'MODIFIED_BY_ID' => $dr['MODIFIED_BY'],
						'MODIFIED_BY_NAME' => $dr['MUSER_NAME'],
						'FILENAME' => $dr['FILENAME'],
						'SITE_ID' => $dr['SITE_ID'],
						'TITLE' => $dr['TITLE'],
						'BODY_HTML' => ($dr['BODY_TYPE'] == 'html' ? $dr['BODY'] : TxtToHTML($dr['BODY'])),
						'BODY_TEXT' => ($dr['BODY_TYPE'] == 'text' ? $dr['BODY'] : HtmlToTxt($dr['BODY'])),
						'BODY' => $dr['BODY'],
						'BODY_TYPE' => $dr['BODY_TYPE'],
						'COMMENTS' => $dr['COMMENTS'],
					];
					CEvent::Send('WF_STATUS_CHANGE', $dr['SITE_ID'], $arEventFields);
				}
				else // otherwise
				{
					// it was new one
					$arEventFields = [
						'ID' => $dr['ID'],
						'ADMIN_EMAIL' => implode(',', $arAdmin),
						'BCC' => implode(',', $arBCC),
						'STATUS_ID' => $dr['STATUS_ID'],
						'STATUS_TITLE' => $dr['STATUS_TITLE'],
						'DATE_ENTER' => $dr['DATE_ENTER'],
						'ENTERED_BY_ID' => $dr['ENTERED_BY'],
						'ENTERED_BY_NAME' => $dr['EUSER_NAME'],
						'ENTERED_BY_EMAIL' => $dr['EUSER_EMAIL'],
						'FILENAME' => $dr['FILENAME'],
						'SITE_ID' => $dr['SITE_ID'],
						'TITLE' => $dr['TITLE'],
						'BODY_HTML' => ($dr['BODY_TYPE'] == 'html' ? $dr['BODY'] : TxtToHTML($dr['BODY'])),
						'BODY_TEXT' => ($dr['BODY_TYPE'] == 'text' ? $dr['BODY'] : HtmlToTxt($dr['BODY'])),
						'BODY' => $dr['BODY'],
						'BODY_TYPE' => $dr['BODY_TYPE'],
						'COMMENTS' => $dr['COMMENTS'],
					];
					CEvent::Send('WF_NEW_DOCUMENT', $dr['SITE_ID'], $arEventFields);
				}
			}
		}
	}

	public static function Delete($DOCUMENT_ID)
	{
		global $DB;

		self::CleanUpFiles($DOCUMENT_ID);
		self::CleanUpPreview($DOCUMENT_ID);
		$DB->Query('DELETE FROM b_workflow_move WHERE DOCUMENT_ID = ' . intval($DOCUMENT_ID));
		$DB->Query('DELETE FROM b_workflow_document WHERE ID = ' . intval($DOCUMENT_ID));
	}

	public static function IsAdmin()
	{
		global $USER;

		if ($USER->IsAdmin())
		{
			return true;
		}
		else
		{
			$WORKFLOW_ADMIN_GROUP_ID = COption::GetOptionString('workflow', 'WORKFLOW_ADMIN_GROUP_ID');
			if (in_array($WORKFLOW_ADMIN_GROUP_ID, $USER->GetUserGroupArray()))
			{
				return true;
			}
		}

		return false;
	}

	// check edit rights for the document
	// depending on it's status and lock
	public static function IsAllowEdit($DOCUMENT_ID, &$locked_by, &$date_lock, $CHECK_RIGHTS='Y')
	{

		$DOCUMENT_ID = intval($DOCUMENT_ID);
		$LOCK_STATUS = self::GetLockStatus($DOCUMENT_ID, $locked_by, $date_lock);
		if ($LOCK_STATUS == 'red')
		{
			return false;
		}
		if ($LOCK_STATUS == 'yellow')
		{
			return true;
		}
		if ($LOCK_STATUS == 'green')
		{
			if ($CHECK_RIGHTS == 'Y')
			{
				return self::IsHaveEditRights($DOCUMENT_ID);
			}
			else
			{
				return true;
			}
		}

		return false;
	}

	public static function GetStatus($DOCUMENT_ID)
	{
		global $DB;

		$DOCUMENT_ID = intval($DOCUMENT_ID);
		$strSql = '
			SELECT
				S.*
			FROM
				b_workflow_document D,
				b_workflow_status S
			WHERE
				D.ID=' . $DOCUMENT_ID . '
			and	S.ID = D.STATUS_ID
			';
		$z = $DB->Query($strSql);

		return $z;
	}

	// check edit rights for the document
	// check is based only on status no lock
	public static function IsHaveEditRights($DOCUMENT_ID)
	{
		global $DB, $USER;

		if (self::IsAdmin())
		{
			return true;
		}

		$arGroups = $USER->GetUserGroupArray();
		if (!is_array($arGroups) || count($arGroups) <= 0)
		{
			$arGroups = [2];
		}

		$strSql = '
			SELECT
				G.ID
			FROM
				b_workflow_document D,
				b_workflow_status2group G
			WHERE
				D.ID = ' . intval($DOCUMENT_ID) . "
				and G.STATUS_ID = D.STATUS_ID
				and G.PERMISSION_TYPE >= '2'
				and G.GROUP_ID in (" . implode(', ', $arGroups) . ')
		';
		$z = $DB->Query($strSql);

		if ($zr = $z->Fetch())
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	public static function UnLock($DOCUMENT_ID)
	{
		global $DB, $USER;

		$DOCUMENT_ID = intval($DOCUMENT_ID);
		$z = self::GetByID($DOCUMENT_ID);
		$zr = $z->Fetch();
		if (self::IsAdmin() || $zr['LOCKED_BY'] == $USER->GetID())
		{
			$arFields = [
				'DATE_LOCK' => 'null',
				'LOCKED_BY' => 'null',
			];
			$rows = $DB->Update('b_workflow_document', $arFields, 'WHERE ID=' . $DOCUMENT_ID);

			return intval($rows);
		}

		return false;
	}

	public static function Lock($DOCUMENT_ID)
	{
		global $DB, $USER;

		$DOCUMENT_ID = intval($DOCUMENT_ID);
		$z = self::GetByID($DOCUMENT_ID);
		if ($zr = $z->Fetch())
		{
			if ($zr['STATUS_ID'] != 1)
			{
				$arFields = [
					'DATE_LOCK' => $DB->GetNowFunction(),
					'LOCKED_BY' => $USER->GetID(),
				];
				$DB->Update('b_workflow_document', $arFields, "WHERE ID='" . $DOCUMENT_ID . "'");
			}
		}
	}

	// return edit link depending on rights and status
	public static function GetEditLink($FILENAME, &$status_id, &$status_title, $template='', $lang=LANGUAGE_ID, $return_url='')
	{
		global $USER;

		$link = '';
		CMain::InitPathVars($SITE_ID, $FILENAME);

		if ($USER->CanDoFileOperation('fm_edit_in_workflow', [$SITE_ID, $FILENAME]))
		{
			//Check if user have access at least to one status
			if (!self::IsAdmin())
			{
				$arGroups = $USER->GetUserGroupArray();
				if (!is_array($arGroups))
				{
					$arGroups = [2];
				}
				$arFilter = [
					'GROUP_ID' => $arGroups,
					'PERMISSION_TYPE_1' => 1,
				];
				$rsStatuses = CWorkflowStatus::GetList('s_c_sort', 'asc', $arFilter, null, ['ID']);
				if (!$rsStatuses->Fetch())
				{
					return '';
				}
			}

			$link = '/bitrix/admin/workflow_edit.php?lang=' . $lang . '&site=' . $SITE_ID . '&fname=' . $FILENAME;
			if ($template <> '')
			{
				$link .= '&template=' . urlencode($template);
			}
			if ($return_url <> '')
			{
				$link .= '&return_url=' . urlencode($return_url);
			}
			$z = self::GetByFilename($FILENAME, $SITE_ID);
			if ($zr = $z->Fetch())
			{
				$status_id = $zr['STATUS_ID'];
				$status_title = $zr['STATUS_TITLE'];
				if ($status_id != 1)
				{
					$DOCUMENT_ID = $zr['ID'];
					if (self::IsHaveEditRights($DOCUMENT_ID))
					{
						$link .= '&ID=' . $DOCUMENT_ID;
					}
					else
					{
						return '';
					}
				}
			}
		}

		return $link;
	}

	public static function DeleteHistory($ID)
	{
		global $DB;
		$DB->Query('
			DELETE FROM b_workflow_log
			WHERE ID = ' . intval($ID) . '
		');
	}

	public static function CleanUp()
	{
		self::CleanUpPublished();
		self::CleanUpHistory();
		self::CleanUpFiles();
		self::CleanUpPreview();

		return 'CWorkflow::CleanUp();';
	}

	public static function CleanUpFiles($DOCUMENT_ID=false, $FILE_ID=false)
	{
		global $DB;

		if ($DOCUMENT_ID === false)
		{
			$strSql = 'SELECT TEMP_FILENAME FROM b_workflow_file WHERE DOCUMENT_ID is null';
		}
		else
		{
			$DOCUMENT_ID = intval($DOCUMENT_ID);
			$strSql = 'SELECT TEMP_FILENAME FROM b_workflow_file WHERE DOCUMENT_ID = ' . $DOCUMENT_ID;
		}
		if ($FILE_ID !== false)
		{
			$FILE_ID = intval($FILE_ID);
			$strSql .= ' and ID = ' . $FILE_ID;
		}
		$z = $DB->Query($strSql);
		while ($zr = $z->Fetch())
		{
			self::DeleteFile($zr['TEMP_FILENAME']);
		}
	}

	public static function CleanUpPreview($DOCUMENT_ID=false)
	{
		global $DB;

		if ($DOCUMENT_ID === false)
		{
			$strSql = '
				SELECT
					P.FILENAME, D.SITE_ID
				FROM
					b_workflow_document D,
					b_workflow_preview P
				WHERE
					D.STATUS_ID = 1
					and P.DOCUMENT_ID = D.ID
				';
		}
		else
		{
			$DOCUMENT_ID = intval($DOCUMENT_ID);
			$strSql = '
				SELECT
					FILENAME
				FROM
					b_workflow_preview
				WHERE
					DOCUMENT_ID = ' . $DOCUMENT_ID . '
				';
		}
		$z = $DB->Query($strSql);
		while ($zr = $z->Fetch())
		{
			self::DeletePreview($zr['FILENAME'], $zr['SITE']);
		}
	}

	public static function DeletePreview($FILENAME, $site = false)
	{
		global $DB;

		$strSql = "DELETE FROM b_workflow_preview WHERE FILENAME='" . $DB->ForSql($FILENAME, 255) . "'";
		$DB->Query($strSql);
		$DOC_ROOT = \Bitrix\Main\SiteTable::getDocumentRoot($site === false ? null : $site);
		$path = $DOC_ROOT . $FILENAME;
		if (file_exists($path))
		{
			unlink($path);
		}
	}

	public static function DeleteFile($FILENAME)
	{
		global $DB;

		$strSql = "DELETE FROM b_workflow_file WHERE TEMP_FILENAME='" . $DB->ForSql($FILENAME, 255) . "'";
		$DB->Query($strSql);
		$temp_path = self::GetTempDir() . $FILENAME;
		if (file_exists($temp_path))
		{
			unlink($temp_path);
		}
	}

	public static function IsFilenameExists($FILENAME)
	{
		global $DB;

		$strSql = "SELECT ID FROM b_workflow_file WHERE TEMP_FILENAME='" . $DB->ForSql($FILENAME, 255) . "'";
		$z = $DB->Query($strSql);
		$zr = $z->Fetch();

		return intval($zr['ID']);
	}

	public static function GetUniqueFilename($filename)
	{
		$ext = GetFileExtension($filename);
		$temp_file = md5($filename . uniqid(rand())) . '.' . $ext;
		while (self::IsFilenameExists($temp_file))
		{
			$temp_file = md5($filename . uniqid(rand())) . '.' . $ext;
		}

		return $temp_file;
	}

	public static function IsPreviewExists($FILENAME)
	{
		global $DB;

		$z = $DB->Query("
			SELECT ID
			FROM b_workflow_preview
			WHERE FILENAME='" . $DB->ForSql($FILENAME, 255) . "'
		");

		$zr = $z->Fetch();

		return intval($zr['ID']);
	}

	public static function GetUniquePreview($DOCUMENT_ID)
	{
		global $DB;

		$z = $DB->Query('
			SELECT FILENAME
			FROM b_workflow_document
			WHERE ID = ' . intval($DOCUMENT_ID) . '
		');

		$zr = $z->Fetch();
		if ($zr)
		{
			$DOCUMENT_PATH = GetDirPath($zr['FILENAME']);
			do
			{
				$temp_file = $DOCUMENT_PATH . md5(uniqid(rand())) . '.php';
			}
			while (self::IsPreviewExists($temp_file));
		}

		return $temp_file;
	}

	public static function SetStatus($DOCUMENT_ID, $STATUS_ID, $OLD_STATUS_ID, $history=true)
	{
		global $DB, $APPLICATION, $USER, $strError;

		//$arMsg = Array();
		$DOCUMENT_ID = intval($DOCUMENT_ID);
		$STATUS_ID = intval($STATUS_ID);
		$OLD_STATUS_ID = intval($OLD_STATUS_ID);
		if ($STATUS_ID == 1) // if "[1] Published"
		{
			// get all files associated with the document
			$files = self::GetFileList($DOCUMENT_ID);
			while ($file = $files->Fetch())
			{
				$path = $file['FILENAME'];
				$DOC_ROOT = \Bitrix\Main\SiteTable::getDocumentRoot($file['SITE_ID'] === false ? null : $file['SITE_ID']);
				$pathto = $DOC_ROOT . $path;
				$pathfrom = self::GetTempDir() . $file['TEMP_FILENAME'];
				if (
					$USER->CanDoFileOperation('fm_edit_in_workflow', [$file['SITE_ID'], $path])
					&& $USER->CanDoFileOperation('fm_edit_existent_file', [$file['SITE_ID'], $path])
					&& $USER->CanDoFileOperation('fm_create_new_file', [$file['SITE_ID'], $path])
				)
				{
					if (!copy($pathfrom, $pathto))
					{
						$str = GetMessage('FLOW_CAN_NOT_WRITE_FILE', ['#FILENAME#' => $path]);
						$strError .= $str . '<br>';
					}
				}
				else
				{
					$str = GetMessage('FLOW_ACCESS_DENIED_FOR_FILE_WRITE', ['#FILENAME#' => $path]);
					$strError .= $str . '<br>';
				}
			}

			// still good
			if ($strError == '')
			{
				// publish the document
				$y = self::GetByID($DOCUMENT_ID);
				$yr = $y->Fetch();
				if (
					$USER->CanDoFileOperation('fm_edit_in_workflow', [$yr['SITE_ID'], $yr['FILENAME']])
					&& $USER->CanDoFileOperation('fm_edit_existent_file', [$yr['SITE_ID'], $yr['FILENAME']])
					&& $USER->CanDoFileOperation('fm_create_new_file', [$yr['SITE_ID'], $yr['FILENAME']])
				)
				{
					// save file
					$prolog = $yr['PROLOG'];
					if ($prolog <> '')
					{
						$title = $yr['TITLE'];
						$prolog = SetPrologTitle($prolog, $title);
					}
					$content = ($yr['BODY_TYPE'] == 'text') ? TxtToHTML($yr['BODY']) : $yr['BODY'];
					$content = WFToPath($content);
					$epilog = $yr['EPILOG'];
					$filesrc = $prolog . $content . $epilog;
					global $BX_WORKFLOW_PUBLISHED_PATH, $BX_WORKFLOW_PUBLISHED_SITE;
					$BX_WORKFLOW_PUBLISHED_PATH = $yr['FILENAME'];
					$BX_WORKFLOW_PUBLISHED_SITE = $yr['SITE_ID'];
					$DOC_ROOT = \Bitrix\Main\SiteTable::getDocumentRoot($yr['SITE_ID'] === false ? null : $yr['SITE_ID']);
					$APPLICATION->SaveFileContent($DOC_ROOT . $yr['FILENAME'], $filesrc);
					$BX_WORKFLOW_PUBLISHED_PATH = '';
					$BX_WORKFLOW_PUBLISHED_SITE = '';
				}
				else // otherwise
				{
					// throw error
					$str = GetMessage('FLOW_ACCESS_DENIED_FOLDER', ['#FILENAME#' => $yr['FILENAME']]);
					$strError .= GetMessage('FLOW_ERROR') . htmlspecialcharsbx($str) . '<br>';
				}
			}
		}

		if ($strError == '')
		{
			// update db
			$arFields = [
				'DATE_MODIFY' => $DB->GetNowFunction(),
				'MODIFIED_BY' => $USER->GetID(),
				'STATUS_ID' => intval($STATUS_ID),
			];
			$DB->Update('b_workflow_document', $arFields, 'WHERE ID=' . $DOCUMENT_ID);
			if ($history === true)
			{
				$LOG_ID = self::SetHistory($DOCUMENT_ID);
				self::SetMove($DOCUMENT_ID, $STATUS_ID, $OLD_STATUS_ID, $LOG_ID);
			}
		}
		else
		{
			$strError = GetMessage('FLOW_DOCUMENT_NOT_PUBLISHED') . '<br>' . $strError;
		}
		self::CleanUpPublished();
	}

	public static function LinkFiles2Document($arUploadedFiles, $DOCUMENT_ID)
	{
		global $DB;

		$DOCUMENT_ID = intval($DOCUMENT_ID);
		if (is_array($arUploadedFiles) && count($arUploadedFiles) > 0)
		{
			foreach ($arUploadedFiles as $FILE_ID)
			{
				$FILE_ID = intval($FILE_ID);
				$strSql = 'UPDATE b_workflow_file SET DOCUMENT_ID=' . $DOCUMENT_ID . ' WHERE ID=' . $FILE_ID;
				$DB->Query($strSql);
			}
		}
		self::CleanUpFiles();
	}

	public static function GetFileByID($DOCUMENT_ID, $FILENAME)
	{
		global $DB;

		$DOCUMENT_ID = intval($DOCUMENT_ID);
		$strSql = '
			SELECT
				F.*
			FROM
				b_workflow_file F
			WHERE
				F.DOCUMENT_ID = ' . $DOCUMENT_ID . "
			and F.FILENAME = '" . $DB->ForSql($FILENAME, 255) . "'
			";
		$z = $DB->Query($strSql);

		return $z;
	}

	public static function GetTempDir()
	{
		$upload_dir = COption::GetOptionString('', 'upload_dir', '/upload/');
		$dir = $_SERVER['DOCUMENT_ROOT'] . '/' . $upload_dir . '/workflow/';
		$dir = str_replace('//', '/', $dir);

		return $dir;
	}

	public static function GetFileContent($did, $fname, $wf_path='', $site=false)
	{
		global $DB, $APPLICATION, $USER;

		$did = intval($did);
		$io = CBXVirtualIo::GetInstance();

		// check if executable
		if (
			$USER->IsAdmin()
			|| (
				$io->ValidatePathString($fname)
				&& !HasScriptExtension($fname)
			)
		)
		{
			if ($did > 0)
			{
				// check if it is associated wtih document
				$z = self::GetFileByID($did, $fname);
				// found one
				if ($zr = $z->Fetch())
				{
					// get it's contents
					$path = self::GetTempDir() . $zr['TEMP_FILENAME'];
					if (file_exists($path))
					{
						return $APPLICATION->GetFileContent($path);
					}
				}
				else
				{
					// lookup in database
					$strSql = 'SELECT FILENAME, SITE_ID FROM b_workflow_document WHERE ID = ' . $did;
					$y = $DB->Query($strSql);
					// found
					if ($yr = $y->Fetch())
					{
						// get it's directory
						$path = GetDirPath($yr['FILENAME']);
						// absolute path
						$pathto = Rel2Abs($path, $fname);
						$DOC_ROOT = \Bitrix\Main\SiteTable::getDocumentRoot($yr['SITE_ID'] === false ? null : $yr['SITE_ID']);
						$path = $DOC_ROOT . $pathto;
						// give it another try
						$u = self::GetFileByID($did, $pathto);
						// found
						if ($ur = $u->Fetch())
						{
							// get it's contents
							$path = self::GetTempDir() . $ur['TEMP_FILENAME'];
							if (file_exists($path))
							{
								return $APPLICATION->GetFileContent($path);
							}
						}
						elseif (file_exists($path)) // it is already on disk
						{
							// get it's contents
							if ($USER->CanDoFileOperation('fm_view_file', [$yr['SITE_ID'], $pathto]))
							{
								return $APPLICATION->GetFileContent($path);
							}
						}
					}
				}
			}
			$DOC_ROOT = \Bitrix\Main\SiteTable::getDocumentRoot($site === false ? null : $site);
			// new one
			if ($wf_path <> '')
			{
				$pathto = Rel2Abs($wf_path, $fname);
				$path = $DOC_ROOT . $pathto;
				if (file_exists($path)) // it is already on disk
				{
					// get it's contents
					if ($USER->CanDoFileOperation('fm_view_file', [$site, $pathto]))
					{
						$src = $APPLICATION->GetFileContent($path);

						return $src;
					}
				}
			}

			// still failed to find
			// get path
			$path = $DOC_ROOT . $fname;
			if (file_exists($path))
			{
				// get it's contents
				if ($USER->CanDoFileOperation('fm_view_file', [$site, $fname]))
				{
					return $APPLICATION->GetFileContent($path);
				}
			}
		}
// it is executable
		else
		{
			return GetMessage('FLOW_ACCESS_DENIED_PHP_VIEW');
		}
	}

	public static function __CheckSite($site)
	{
		if ($site !== false)
		{
			if ($site <> '')
			{
				$res = CSite::GetByID($site);
				if (!$res->Fetch())
				{
					$site = false;
				}
			}
			else
			{
				$site = false;
			}
		}

		return $site;
	}

	public static function Insert($arFields)
	{
		global $DB;

		$arFields['~DATE_MODIFY'] = $DB->CurrentTimeFunction();
		$arFields['~DATE_ENTER'] = $DB->CurrentTimeFunction();
		$ID = $DB->Add('b_workflow_document', $arFields, [], 'workflow');
		$LOG_ID = self::SetHistory($ID);
		self::SetMove($ID, $arFields['STATUS_ID'], 0, $LOG_ID);

		return $ID;
	}

	public static function Update($arFields, $DOCUMENT_ID)
	{
		global $DB;

		$z = self::GetByID($DOCUMENT_ID);
		$change = false;
		if ($zr = $z->Fetch())
		{
			if (
				$zr['STATUS_ID'] != $arFields['STATUS_ID']
				|| $zr['BODY'] != $arFields['BODY']
				|| $zr['BODY_TYPE'] != $arFields['BODY_TYPE']
				|| $zr['COMMENTS'] != $arFields['COMMENTS']
				|| $zr['FILENAME'] != $arFields['FILENAME']
				|| $zr['SITE_ID'] != $arFields['SITE_ID']
				|| $zr['TITLE'] != $arFields['TITLE']
			)
			{
				$change = true;
			}
		}

		$strUpdate = $DB->PrepareUpdate('b_workflow_document', $arFields, 'workflow');
		if ($strUpdate)
		{
			$DB->Query('
				UPDATE b_workflow_document
				SET ' . $strUpdate . ', DATE_MODIFY=now(), DATE_ENTER=now()
				WHERE ID = ' . $DOCUMENT_ID,
			);
		}

		if ($change)
		{
			$LOG_ID = self::SetHistory($DOCUMENT_ID);
			self::SetMove($DOCUMENT_ID, $arFields['STATUS_ID'], $zr['STATUS_ID'], $LOG_ID);
		}
	}

	public static function GetLockStatus($DOCUMENT_ID, &$locked_by, &$date_lock)
	{
		global $DB, $USER;
		$connection = \Bitrix\Main\Application::getConnection();
		$helper = $connection->getSqlHelper();

		$DOCUMENT_ID = intval($DOCUMENT_ID);
		$MAX_LOCK = COption::GetOptionInt('workflow', 'MAX_LOCK_TIME', '60');
		$uid = intval($USER->GetID());
		$strSql = '
			SELECT
				LOCKED_BY,
				' . $DB->DateToCharFunction('DATE_LOCK') . " DATE_LOCK,
				case
				when DATE_LOCK is null then 'green'
				when " . $helper->addSecondsToDateTime($MAX_LOCK * 60, 'DATE_LOCK') . " < now() then 'green'
				when LOCKED_BY = " . $uid . " then 'yellow'
				else 'red' end LOCK_STATUS
			FROM
				b_workflow_document
			WHERE
				ID = " . $DOCUMENT_ID . '
			';
		$z = $DB->Query($strSql);
		$zr = $z->Fetch();
		$locked_by = $zr ? $zr['LOCKED_BY'] : 0;
		$date_lock = $zr? $zr['DATE_LOCK'] : false;

		return $zr['LOCK_STATUS'];
	}

	public static function GetList($by = 's_date_modify', $order = 'desc', $arFilter = [])
	{
		global $DB, $USER;
		$connection = \Bitrix\Main\Application::getConnection();
		$helper = $connection->getSqlHelper();

		$obQueryWhere = new CSQLWhere();
		static $arWhereFields = [
			'ID' => [
				'TABLE_ALIAS' => 'D',
				'FIELD_NAME' => 'D.ID',
				'FIELD_TYPE' => 'int', //int, double, file, enum, int, string, date, datetime
				'JOIN' => false,
			],
			'MODIFIED_BY' => [
				'TABLE_ALIAS' => 'D',
				'FIELD_NAME' => 'D.MODIFIED_BY',
				'FIELD_TYPE' => 'int',
				'JOIN' => false,
			],
			'STATUS_ID' => [
				'TABLE_ALIAS' => 'D',
				'FIELD_NAME' => 'D.STATUS_ID',
				'FIELD_TYPE' => 'int',
				'JOIN' => false,
			],
		];
		$obQueryWhere->SetFields($arWhereFields);

		$arSqlSearch = [];
		$MAX_LOCK = COption::GetOptionInt('workflow', 'MAX_LOCK_TIME', '60');
		$arGroups = $USER->GetUserGroupArray();
		if (!is_array($arGroups))
		{
			$arGroups[] = 2;
		}
		$groups = implode(', ', $arGroups);
		$uid = intval($USER->GetID());
		if (is_array($arFilter))
		{
			foreach ($arFilter as $key => $val)
			{
				if ((string)$val == '' || (string)$val == 'NOT_REF')
				{
					continue;
				}
				if (is_array($val) && count($val) <= 0)
				{
					continue;
				}
				$match_value_set = array_key_exists($key . '_EXACT_MATCH', $arFilter);
				$key = strtoupper($key);
				switch ($key)
				{
					case 'ID':
					case 'STATUS_ID':
						$arSqlSearch[] = $obQueryWhere->GetQuery([$key => $val]);

						break;
					case 'DATE_MODIFY_1':
						if (CheckDateTime($val))
						{
							$arSqlSearch[] = 'D.DATE_MODIFY >= ' . $DB->CharToDateFunction($val, 'SHORT');
						}

						break;
					case 'DATE_MODIFY_2':
						if (CheckDateTime($val))
						{
							$arSqlSearch[] = 'D.DATE_MODIFY < ' . $DB->CharToDateFunction($val, 'SHORT') . ' + INTERVAL 1 DAY';
						}

						break;
					case 'MODIFIED_BY':
						$match = ($match_value_set && $arFilter[$key . '_EXACT_MATCH'] == 'Y') ? 'N' : 'Y';
						$filter = GetFilterQuery('UM.LOGIN, UM.NAME, UM.LAST_NAME', $val, $match);
						if ($filter)
						{
							$arSqlSearch[] = $obQueryWhere->GetQuery(['MODIFIED_BY' => $val]) . ' or ' . $filter;
						}

						break;
					case 'MODIFIED_USER_ID':
						$arSqlSearch[] = $obQueryWhere->GetQuery(['MODIFIED_BY' => $val]);

						break;
					case 'LOCK_STATUS':
						$arSqlSearch[] = "
							case
								when D.DATE_LOCK is null then 'green'
								when " . $helper->addSecondsToDateTime($MAX_LOCK * 60, 'D.DATE_LOCK') . " < now() then 'green'
								when D.LOCKED_BY = " . $uid . " then 'yellow'
								else 'red' end = '" . $DB->ForSql($val) . "'";

						break;
					case 'STATUS':
						$match = ($match_value_set && $arFilter[$key . '_EXACT_MATCH'] == 'Y') ? 'N' : 'Y';
						$filter = GetFilterQuery('S.TITLE', $val, $match);
						if ($filter)
						{
							$arSqlSearch[] = $obQueryWhere->GetQuery(['STATUS_ID' => $val]) . ' or ' . $filter;
						}

						break;
					case 'SITE_ID':
					case 'TITLE':
					case 'BODY':
						$match = ($match_value_set && $arFilter[$key . '_EXACT_MATCH'] == 'Y') ? 'N' : 'Y';
						$arSqlSearch[] = GetFilterQuery('D.' . $key, $val, $match);

						break;
					case 'FILENAME':
						$match = ($match_value_set && $arFilter[$key . '_EXACT_MATCH'] == 'Y') ? 'N' : 'Y';
						$arSqlSearch[] = GetFilterQuery('D.FILENAME', $val, $match, ['/', '\\', '.', '_']);

						break;
				}
			}
		}

		if ($by === 's_id')
		{
			$strSqlOrder = 'ORDER BY D.ID';
		}
		elseif ($by === 's_lock_status')
		{
			$strSqlOrder = 'ORDER BY LOCK_STATUS';
		}
		elseif ($by === 's_date_modify')
		{
			$strSqlOrder = 'ORDER BY D.DATE_MODIFY';
		}
		elseif ($by === 's_modified_by')
		{
			$strSqlOrder = 'ORDER BY D.MODIFIED_BY';
		}
		elseif ($by === 's_filename')
		{
			$strSqlOrder = 'ORDER BY D.FILENAME';
		}
		elseif ($by === 's_title')
		{
			$strSqlOrder = 'ORDER BY D.TITLE';
		}
		elseif ($by === 's_site_id')
		{
			$strSqlOrder = 'ORDER BY D.SITE_ID';
		}
		elseif ($by === 's_status')
		{
			$strSqlOrder = 'ORDER BY D.STATUS_ID';
		}
		else
		{
			$strSqlOrder = 'ORDER BY D.DATE_MODIFY';
		}

		if ($order != 'asc')
		{
			$strSqlOrder .= ' desc ';
		}

		$strSqlSearch = GetFilterSqlSearch($arSqlSearch);
		if (self::IsAdmin())
		{
			$strSql = '
				SELECT DISTINCT
					D.*,
					' . $DB->DateToCharFunction('D.DATE_ENTER') . ' DATE_ENTER,
					' . $DB->DateToCharFunction('D.DATE_MODIFY') . ' DATE_MODIFY,
					' . $DB->DateToCharFunction('D.DATE_LOCK') . " DATE_LOCK,
					concat_ws(' ', " . $DB->Concat("'('", 'UM.LOGIN', "')'") . ", nullif(UM.NAME, ''), nullif(UM.LAST_NAME, '')) MUSER_NAME,
					concat_ws(' ', " . $DB->Concat("'('", 'UE.LOGIN', "')'") . ", nullif(UE.NAME, ''), nullif(UE.LAST_NAME, '')) EUSER_NAME,
					S.TITLE STATUS_TITLE,
					case
						when D.DATE_LOCK is null then 'green'
						when " . $helper->addSecondsToDateTime($MAX_LOCK * 60, 'D.DATE_LOCK') . " < now() then 'green'
						when D.LOCKED_BY = " . $uid . " then 'yellow'
					else 'red' end LOCK_STATUS
				FROM
					b_workflow_document D
					LEFT JOIN b_workflow_status S ON (S.ID = D.STATUS_ID)
					LEFT JOIN b_user UM ON (UM.ID = D.MODIFIED_BY)
					LEFT JOIN b_user UE ON (UE.ID = D.ENTERED_BY)
				WHERE
				" . $strSqlSearch . '
				' . $strSqlOrder . '
				';
		}
		else
		{
			$strSql = '
				SELECT DISTINCT
					D.*,
					' . $DB->DateToCharFunction('D.DATE_ENTER') . ' DATE_ENTER,
					' . $DB->DateToCharFunction('D.DATE_MODIFY') . ' DATE_MODIFY,
					' . $DB->DateToCharFunction('D.DATE_LOCK') . " DATE_LOCK,
					concat_ws(' ', " . $DB->Concat("'('", 'UM.LOGIN', "')'") . ", nullif(UM.NAME, ''), nullif(UM.LAST_NAME, '')) MUSER_NAME,
					concat_ws(' ', " . $DB->Concat("'('", 'UE.LOGIN', "')'") . ", nullif(UE.NAME, ''), nullif(UE.LAST_NAME, '')) EUSER_NAME,
					S.TITLE STATUS_TITLE,
					case
						when D.DATE_LOCK is null then 'green'
						when " . $helper->addSecondsToDateTime($MAX_LOCK * 60, 'D.DATE_LOCK') . " < now() then 'green'
						when D.LOCKED_BY = " . $uid . " then 'yellow'
					else 'red' end LOCK_STATUS
				FROM
					b_workflow_document D
					INNER JOIN b_workflow_status2group G ON (G.STATUS_ID = D.STATUS_ID)
					LEFT JOIN b_workflow_status S ON (S.ID = D.STATUS_ID)
					LEFT JOIN b_user UM ON (UM.ID = D.MODIFIED_BY)
					LEFT JOIN b_user UE ON (UE.ID = D.ENTERED_BY)
				WHERE
				" . $strSqlSearch . '
				and G.GROUP_ID in (' . $groups . ")
				and G.PERMISSION_TYPE >= '2'
				" . $strSqlOrder . '
				';
		}

		$rs = $DB->Query($strSql);
		$arr = [];
		while ($ar = $rs->Fetch())
		{
			if ($USER->CanDoFileOperation('fm_edit_in_workflow', [$ar['SITE_ID'], $ar['FILENAME']]))
			{
				$arr[] = $ar;
			}
		}
		$rs = new CDBResult();
		$rs->InitFromArray($arr);

		return $rs;
	}

	public static function GetByID($ID)
	{
		global $DB, $USER;
		$connection = \Bitrix\Main\Application::getConnection();
		$helper = $connection->getSqlHelper();

		$ID = intval($ID);
		$MAX_LOCK = COption::GetOptionInt('workflow', 'MAX_LOCK_TIME', '60');
		$uid = intval($USER->GetID());
		$strSql = '
			SELECT
				D.*,
				' . $DB->DateToCharFunction('D.DATE_ENTER') . ' DATE_ENTER,
				' . $DB->DateToCharFunction('D.DATE_MODIFY') . ' DATE_MODIFY,
				' . $DB->DateToCharFunction('D.DATE_LOCK') . " DATE_LOCK,
				concat_ws(' ', " . $DB->Concat("'('", 'UM.LOGIN', "')'") . ", nullif(UM.NAME, ''), nullif(UM.LAST_NAME, '')) MUSER_NAME,
				concat_ws(' ', " . $DB->Concat("'('", 'UE.LOGIN', "')'") . ", nullif(UE.NAME, ''), nullif(UE.LAST_NAME, '')) EUSER_NAME,
				concat_ws(' ', " . $DB->Concat("'('", 'UL.LOGIN', "')'") . ", nullif(UL.NAME, ''), nullif(UL.LAST_NAME, '')) LUSER_NAME,
				UE.EMAIL EUSER_EMAIL,
				S.TITLE STATUS_TITLE,
				case
					when D.DATE_LOCK is null then 'green'
					when " . $helper->addSecondsToDateTime($MAX_LOCK * 60, 'D.DATE_LOCK') . " < now() then 'green'
					when D.LOCKED_BY = " . $uid . " then 'yellow'
				else 'red' end LOCK_STATUS
			FROM
				b_workflow_document D
				LEFT JOIN b_user UM ON (UM.ID = D.MODIFIED_BY)
				LEFT JOIN b_user UE ON (UE.ID = D.ENTERED_BY)
				LEFT JOIN b_user UL ON (UL.ID = D.LOCKED_BY)
				LEFT JOIN b_workflow_status S ON (S.ID = D.STATUS_ID)
			WHERE
				D.ID = " . $ID . '
			';
		$res = $DB->Query($strSql);

		return $res;
	}

	public static function GetByFilename($FILENAME, $SITE_ID, $arFilter = false)
	{
		if (!is_array($arFilter))
		{
			$arFilter = [
				'!STATUS_ID' => 1,
			];
		}

		$obQueryWhere = new CSQLWhere();
		$obQueryWhere->SetFields([
			'STATUS_ID' => [
				'TABLE_ALIAS' => 'D',
				'FIELD_NAME' => 'D.STATUS_ID',
				'FIELD_TYPE' => 'int',
				'JOIN' => false,
			],
		]);
		$strSqlWhere = $obQueryWhere->GetQuery($arFilter);

		global $DB, $USER;
		$connection = \Bitrix\Main\Application::getConnection();
		$helper = $connection->getSqlHelper();

		$MAX_LOCK = COption::GetOptionInt('workflow', 'MAX_LOCK_TIME', '60');
		$uid = intval($USER->GetID());
		$strSql = '
			SELECT
				D.*,
				' . $DB->DateToCharFunction('D.DATE_ENTER') . ' DATE_ENTER,
				' . $DB->DateToCharFunction('D.DATE_MODIFY') . ' DATE_MODIFY,
				' . $DB->DateToCharFunction('D.DATE_LOCK') . " DATE_LOCK,
				concat_ws(' ', " . $DB->Concat("'('", 'UM.LOGIN', "')'") . ", nullif(UM.NAME, ''), nullif(UM.LAST_NAME, '')) MUSER_NAME,
				concat_ws(' ', " . $DB->Concat("'('", 'UE.LOGIN', "')'") . ", nullif(UE.NAME, ''), nullif(UE.LAST_NAME, '')) EUSER_NAME,
				concat_ws(' ', " . $DB->Concat("'('", 'UL.LOGIN', "')'") . ", nullif(UL.NAME, ''), nullif(UL.LAST_NAME, '')) LUSER_NAME,
				S.TITLE STATUS_TITLE,
				case
					when D.DATE_LOCK is null then 'green'
					when " . $helper->addSecondsToDateTime($MAX_LOCK * 60, 'D.DATE_LOCK') . " < now() then 'green'
					when D.LOCKED_BY = " . $uid . " then 'yellow'
				else 'red' end LOCK_STATUS
			FROM
				b_workflow_document D
				LEFT JOIN b_user UM ON (UM.ID = D.MODIFIED_BY)
				LEFT JOIN b_user UE ON (UE.ID = D.ENTERED_BY)
				LEFT JOIN b_user UL ON (UL.ID = D.LOCKED_BY)
				LEFT JOIN b_workflow_status S ON (S.ID = D.STATUS_ID)
			WHERE
				SITE_ID = '" . $DB->ForSql($SITE_ID, 2) . "'
				AND D.FILENAME = '" . $DB->ForSql($FILENAME, 255) . "'
				" . ($strSqlWhere ? 'AND ' . $strSqlWhere : '') . '
		';
		$res = $DB->Query($strSql);

		return $res;
	}

	public static function GetHistoryList($by = 's_id', $order = 'desc', $arFilter = [])
	{
		global $DB;
		$connection = \Bitrix\Main\Application::getConnection();
		$helper = $connection->getSqlHelper();

		$obQueryWhere = new CSQLWhere();
		static $arWhereFields = [
			'ID' => [
				'TABLE_ALIAS' => 'L',
				'FIELD_NAME' => 'L.ID',
				'FIELD_TYPE' => 'int', //int, double, file, enum, int, string, date, datetime
				'JOIN' => false,
			],
			'DOCUMENT_ID' => [
				'TABLE_ALIAS' => 'L',
				'FIELD_NAME' => 'L.DOCUMENT_ID',
				'FIELD_TYPE' => 'int',
				'JOIN' => false,
			],
			'MODIFIED_BY' => [
				'TABLE_ALIAS' => 'L',
				'FIELD_NAME' => 'L.MODIFIED_BY',
				'FIELD_TYPE' => 'int',
				'JOIN' => false,
			],
			'STATUS_ID' => [
				'TABLE_ALIAS' => 'L',
				'FIELD_NAME' => 'L.STATUS_ID',
				'FIELD_TYPE' => 'int',
				'JOIN' => false,
			],
		];
		$obQueryWhere->SetFields($arWhereFields);

		$arSqlSearch = [];
		if (is_array($arFilter))
		{
			foreach ($arFilter as $key => $val)
			{
				if ((string)$val == '' || (string)$val == 'NOT_REF')
				{
					continue;
				}
				if (is_array($val) && !$val)
				{
					continue;
				}
				$match_value_set = array_key_exists($key . '_EXACT_MATCH', $arFilter);
				$key = strtoupper($key);
				switch ($key)
				{
					case 'ID':
					case 'DOCUMENT_ID':
					case 'MODIFIED_BY':
					case 'STATUS_ID':
						$arSqlSearch[] = $obQueryWhere->GetQuery([$key => $val]);

						break;
					case 'DATE_MODIFY_1':
						if (CheckDateTime($val))
						{
							$arSqlSearch[] = 'L.TIMESTAMP_X >= ' . $DB->CharToDateFunction($val, 'SHORT');
						}

						break;
					case 'DATE_MODIFY_2':
						if (CheckDateTime($val))
						{
							$arSqlSearch[] = 'L.TIMESTAMP_X < ' . $helper->addDaysToDateTime(1, $DB->CharToDateFunction($val, 'SHORT'));
						}

						break;
					case 'MODIFIED_USER':
						$match = ($match_value_set && $arFilter[$key . '_EXACT_MATCH'] == 'Y') ? 'N' : 'Y';
						$filter = GetFilterQuery('U.LOGIN, U.NAME, U.LAST_NAME', $val, $match);
						if ($filter)
						{
							$arSqlSearch[] = $obQueryWhere->GetQuery(['MODIFIED_BY' => $val]) . ' or ' . $filter;
						}

						break;
					case 'TITLE':
					case 'SITE_ID':
						$match = ($match_value_set && $arFilter[$key . '_EXACT_MATCH'] == 'Y') ? 'N' : 'Y';
						$arSqlSearch[] = GetFilterQuery('L.' . $key, $val, $match);

						break;
					case 'FILENAME':
						$match = ($match_value_set && $arFilter[$key . '_EXACT_MATCH'] == 'Y') ? 'N' : 'Y';
						$arSqlSearch[] = GetFilterQuery('L.FILENAME', $val, $match, ['/', '\\', '.', '_']);

						break;
					case 'BODY':
						$match = ($match_value_set && $arFilter[$key . '_EXACT_MATCH'] == 'N') ? 'Y' : 'N';
						$arSqlSearch[] = GetFilterQuery('L.BODY', $val, $match, [], 'Y');

						break;
					case 'STATUS':
						$match = ($match_value_set && $arFilter[$key . '_EXACT_MATCH'] == 'Y') ? 'N' : 'Y';
						$filter = GetFilterQuery('S.TITLE', $val, $match);
						if ($filter)
						{
							$arSqlSearch[] = $obQueryWhere->GetQuery(['STATUS_ID' => $val]) . ' or ' . $filter;
						}

						break;
				}
			}
		}

		if ($by === 's_id')
		{
			$strSqlOrder = 'ORDER BY L.ID';
		}
		elseif ($by === 's_document_id')
		{
			$strSqlOrder = 'ORDER BY L.DOCUMENT_ID';
		}
		elseif ($by === 's_date_modify')
		{
			$strSqlOrder = 'ORDER BY L.TIMESTAMP_X';
		}
		elseif ($by === 's_modified_by')
		{
			$strSqlOrder = 'ORDER BY L.MODIFIED_BY';
		}
		elseif ($by === 's_filename')
		{
			$strSqlOrder = 'ORDER BY L.FILENAME';
		}
		elseif ($by === 's_site_id')
		{
			$strSqlOrder = 'ORDER BY L.SITE_ID';
		}
		elseif ($by === 's_title')
		{
			$strSqlOrder = 'ORDER BY L.TITLE';
		}
		elseif ($by === 's_status')
		{
			$strSqlOrder = 'ORDER BY L.STATUS_ID';
		}
		else
		{
			$strSqlOrder = 'ORDER BY L.ID';
		}

		if ($order != 'asc')
		{
			$strSqlOrder .= ' desc ';
		}

		$strSqlSearch = GetFilterSqlSearch($arSqlSearch);
		$strSql = '
			SELECT DISTINCT
				L.*,
				' . $DB->DateToCharFunction('L.TIMESTAMP_X') . " TIMESTAMP_X,
				concat_ws(' ', " . $DB->Concat("'('", 'U.LOGIN', "')'") . ", nullif(U.NAME, ''), nullif(U.LAST_NAME, '')) USER_NAME,
				S.TITLE STATUS_TITLE
			FROM
				b_workflow_log L
				LEFT JOIN b_workflow_status S ON (S.ID = L.STATUS_ID)
				LEFT JOIN b_user U ON (U.ID = L.MODIFIED_BY)
			WHERE
			" . $strSqlSearch . '
			' . $strSqlOrder . '
			';

		$res = $DB->Query($strSql);

		return $res;
	}

	public static function GetHistoryByID($ID)
	{
		global $DB;

		$ID = intval($ID);
		$strSql = '
			SELECT DISTINCT
				L.*,
				' . $DB->DateToCharFunction('L.TIMESTAMP_X') . " TIMESTAMP_X,
				concat_ws(' ', " . $DB->Concat("'('", 'U.LOGIN', "')'") . ", nullif(U.NAME, ''), nullif(U.LAST_NAME, '')) USER_NAME,
				S.TITLE STATUS_TITLE
			FROM
				b_workflow_log L
				LEFT JOIN b_workflow_status S ON (S.ID = L.STATUS_ID)
				LEFT JOIN b_user U ON (U.ID = L.MODIFIED_BY)
			WHERE
				L.ID = " . $ID . '
		';
		$res = $DB->Query($strSql);

		return $res;
	}

	public static function CleanUpHistory()
	{
		$connection = \Bitrix\Main\Application::getConnection();
		$helper = $connection->getSqlHelper();

		$HISTORY_DAYS = COption::GetOptionInt('workflow', 'HISTORY_DAYS', '-1');
		if ($HISTORY_DAYS >= 0)
		{
			$strSql = 'DELETE FROM b_workflow_log WHERE TIMESTAMP_X < ' . $helper->addDaysToDateTime(-$HISTORY_DAYS);
			$connection->query($strSql);
		}

		if (CModule::IncludeModule('iblock'))
		{
			CIblockElement::WF_CleanUpHistory();
		}
	}

	public static function CleanUpPublished()
	{
		$connection = \Bitrix\Main\Application::getConnection();
		$helper = $connection->getSqlHelper();

		$DAYS_AFTER_PUBLISHING = COption::GetOptionInt('workflow', 'DAYS_AFTER_PUBLISHING', '0');
		if ($DAYS_AFTER_PUBLISHING >= 0)
		{
			$strSql = '
				SELECT
					ID
				FROM
					b_workflow_document
				WHERE
					STATUS_ID = 1
					and DATE_MODIFY < ' . $helper->addDaysToDateTime(-$DAYS_AFTER_PUBLISHING) . '
				';
			$z = $connection->query($strSql);
			while ($zr = $z->fetch())
			{
				self::Delete($zr['ID']);
			}
		}
	}

	public static function GetFileList($DOCUMENT_ID)
	{
		global $DB;

		$DOCUMENT_ID = intval($DOCUMENT_ID);
		$strSql = '
			SELECT
				F.*, D.SITE_ID,
				' . $DB->DateToCharFunction('F.TIMESTAMP_X') . " TIMESTAMP_X,
				concat_ws(' ', " . $DB->Concat("'('", 'U.LOGIN', "')'") . ", nullif(U.NAME, ''), nullif(U.LAST_NAME, '')) USER_NAME
			FROM
				b_workflow_document D
				INNER JOIN b_workflow_file F ON (F.DOCUMENT_ID = D.ID)
				LEFT JOIN b_user U ON (U.ID = F.MODIFIED_BY)
			WHERE
				D.ID = " . $DOCUMENT_ID . '
			ORDER BY
				F.TIMESTAMP_X desc
		';
		$z = $DB->Query($strSql);

		return $z;
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit