diff --git a/api/iclock/cdata b/api/iclock/cdata new file mode 100644 index 0000000..bdb51df --- /dev/null +++ b/api/iclock/cdata @@ -0,0 +1,87 @@ +query("SELECT id, zkteco_uid FROM hr_employees WHERE zkteco_uid IS NOT NULL AND zkteco_uid != '' AND status = 'active'"); + $empMap = []; + while ($emp = $empStmt->fetch()) { + $empMap[(string)$emp['zkteco_uid']] = $emp['id']; + } + + $insertedCount = 0; + foreach ($lines as $line) { + $line = trim($line); + if (empty($line)) continue; + + $parts = explode("\t", $line); + if (count($parts) >= 2) { + $uid = trim($parts[0]); + $datetime = trim($parts[1]); + + if (strlen($datetime) >= 19 && isset($empMap[$uid])) { + $emp_id = $empMap[$uid]; + $date = substr($datetime, 0, 10); + $time = substr($datetime, 11, 8); + + $chkStmt = db()->prepare("SELECT id, check_in, check_out FROM hr_attendance WHERE employee_id = ? AND date = ?"); + $chkStmt->execute([$emp_id, $date]); + $existing = $chkStmt->fetch(); + + if ($existing) { + $upd_in = $existing['check_in']; + $upd_out = $existing['check_out']; + $changed = false; + + if (empty($upd_in) || $time < $upd_in) { $upd_in = $time; $changed = true; } + if ($time > $upd_in && (empty($upd_out) || $time > $upd_out)) { $upd_out = $time; $changed = true; } + + if ($changed) { + $uStmt = db()->prepare("UPDATE hr_attendance SET check_in = ?, check_out = ?, status = 'present' WHERE id = ?"); + $uStmt->execute([$upd_in, $upd_out, $existing['id']]); + } + } else { + $iStmt = db()->prepare("INSERT INTO hr_attendance (employee_id, date, check_in, status) VALUES (?, ?, ?, 'present')"); + $iStmt->execute([$emp_id, $date, $time]); + } + $insertedCount++; + } + } + } + + echo "OK: " . $insertedCount; + exit; + } else { + // Acknowledge OPERLOG or other tables to keep device happy + echo "OK"; + exit; + } +} \ No newline at end of file diff --git a/api/iclock/devicecmd b/api/iclock/devicecmd new file mode 100644 index 0000000..2e4a3a4 --- /dev/null +++ b/api/iclock/devicecmd @@ -0,0 +1,4 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +setupEnvironment(); +process(is_array($argv) ? $argv : array()); + +/** + * Initializes various values + * + * @throws RuntimeException If uopz extension prevents exit calls + */ +function setupEnvironment() +{ + ini_set('display_errors', 1); + + if (extension_loaded('uopz') && !(ini_get('uopz.disable') || ini_get('uopz.exit'))) { + // uopz works at opcode level and disables exit calls + if (function_exists('uopz_allow_exit')) { + @uopz_allow_exit(true); + } else { + throw new RuntimeException('The uopz extension ignores exit calls and breaks this installer.'); + } + } + + $installer = 'ComposerInstaller'; + + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + if ($version = getenv('COMPOSERSETUP')) { + $installer = sprintf('Composer-Setup.exe/%s', $version); + } + } + + define('COMPOSER_INSTALLER', $installer); +} + +/** + * Processes the installer + */ +function process($argv) +{ + // Determine ANSI output from --ansi and --no-ansi flags + setUseAnsi($argv); + + $help = in_array('--help', $argv) || in_array('-h', $argv); + if ($help) { + displayHelp(); + exit(0); + } + + $check = in_array('--check', $argv); + $force = in_array('--force', $argv); + $quiet = in_array('--quiet', $argv); + $channel = 'stable'; + if (in_array('--snapshot', $argv)) { + $channel = 'snapshot'; + } elseif (in_array('--preview', $argv)) { + $channel = 'preview'; + } elseif (in_array('--1', $argv)) { + $channel = '1'; + } elseif (in_array('--2', $argv)) { + $channel = '2'; + } elseif (in_array('--2.2', $argv)) { + $channel = '2.2'; + } + $disableTls = in_array('--disable-tls', $argv); + $installDir = getOptValue('--install-dir', $argv, false); + $version = getOptValue('--version', $argv, false); + $filename = getOptValue('--filename', $argv, 'composer.phar'); + $cafile = getOptValue('--cafile', $argv, false); + + if (!checkParams($installDir, $version, $cafile)) { + exit(1); + } + + $ok = checkPlatform($warnings, $quiet, $disableTls, true); + + if ($check) { + // Only show warnings if we haven't output any errors + if ($ok) { + showWarnings($warnings); + showSecurityWarning($disableTls); + } + exit($ok ? 0 : 1); + } + + if ($ok || $force) { + if ($channel === '1' && !$quiet) { + out('Warning: You forced the install of Composer 1.x via --1, but Composer 2.x is the latest stable version. Updating to it via composer self-update --stable is recommended.', 'error'); + } + + $installer = new Installer($quiet, $disableTls, $cafile); + if ($installer->run($version, $installDir, $filename, $channel)) { + showWarnings($warnings); + showSecurityWarning($disableTls); + exit(0); + } + } + + exit(1); +} + +/** + * Displays the help + */ +function displayHelp() +{ + echo << $value) { + $next = $key + 1; + if (0 === strpos($value, $opt)) { + if ($optLength === strlen($value) && isset($argv[$next])) { + return trim($argv[$next]); + } else { + return trim(substr($value, $optLength + 1)); + } + } + } + + return $default; +} + +/** + * Checks that user-supplied params are valid + * + * @param mixed $installDir The required istallation directory + * @param mixed $version The required composer version to install + * @param mixed $cafile Certificate Authority file + * + * @return bool True if the supplied params are okay + */ +function checkParams($installDir, $version, $cafile) +{ + $result = true; + + if (false !== $installDir && !is_dir($installDir)) { + out("The defined install dir ({$installDir}) does not exist.", 'info'); + $result = false; + } + + if (false !== $version && 1 !== preg_match('/^\d+\.\d+\.\d+(\-(alpha|beta|RC)\d*)*$/', $version)) { + out("The defined install version ({$version}) does not match release pattern.", 'info'); + $result = false; + } + + if (false !== $cafile && (!file_exists($cafile) || !is_readable($cafile))) { + out("The defined Certificate Authority (CA) cert file ({$cafile}) does not exist or is not readable.", 'info'); + $result = false; + } + return $result; +} + +/** + * Checks the platform for possible issues running Composer + * + * Errors are written to the output, warnings are saved for later display. + * + * @param array $warnings Populated by method, to be shown later + * @param bool $quiet Quiet mode + * @param bool $disableTls Bypass tls + * @param bool $install If we are installing, rather than diagnosing + * + * @return bool True if there are no errors + */ +function checkPlatform(&$warnings, $quiet, $disableTls, $install) +{ + getPlatformIssues($errors, $warnings, $install); + + // Make openssl warning an error if tls has not been specifically disabled + if (isset($warnings['openssl']) && !$disableTls) { + $errors['openssl'] = $warnings['openssl']; + unset($warnings['openssl']); + } + + if (!empty($errors)) { + // Composer-Setup.exe uses "Some settings" to flag platform errors + out('Some settings on your machine make Composer unable to work properly.', 'error'); + out('Make sure that you fix the issues listed below and run this script again:', 'error'); + outputIssues($errors); + return false; + } + + if (empty($warnings) && !$quiet) { + out('All settings correct for using Composer', 'success'); + } + return true; +} + +/** + * Checks platform configuration for common incompatibility issues + * + * @param array $errors Populated by method + * @param array $warnings Populated by method + * @param bool $install If we are installing, rather than diagnosing + * + * @return bool If any errors or warnings have been found + */ +function getPlatformIssues(&$errors, &$warnings, $install) +{ + $errors = array(); + $warnings = array(); + + $iniMessage = PHP_EOL.getIniMessage(); + $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.'; + + if (ini_get('detect_unicode')) { + $errors['unicode'] = array( + 'The detect_unicode setting must be disabled.', + 'Add the following to the end of your `php.ini`:', + ' detect_unicode = Off', + $iniMessage + ); + } + + if (extension_loaded('suhosin')) { + $suhosin = ini_get('suhosin.executor.include.whitelist'); + $suhosinBlacklist = ini_get('suhosin.executor.include.blacklist'); + if (false === stripos($suhosin, 'phar') && (!$suhosinBlacklist || false !== stripos($suhosinBlacklist, 'phar'))) { + $errors['suhosin'] = array( + 'The suhosin.executor.include.whitelist setting is incorrect.', + 'Add the following to the end of your `php.ini` or suhosin.ini (Example path [for Debian]: /etc/php5/cli/conf.d/suhosin.ini):', + ' suhosin.executor.include.whitelist = phar '.$suhosin, + $iniMessage + ); + } + } + + if (!function_exists('json_decode')) { + $errors['json'] = array( + 'The json extension is missing.', + 'Install it or recompile php without --disable-json' + ); + } + + if (!extension_loaded('Phar')) { + $errors['phar'] = array( + 'The phar extension is missing.', + 'Install it or recompile php without --disable-phar' + ); + } + + if (!extension_loaded('filter')) { + $errors['filter'] = array( + 'The filter extension is missing.', + 'Install it or recompile php without --disable-filter' + ); + } + + if (!extension_loaded('hash')) { + $errors['hash'] = array( + 'The hash extension is missing.', + 'Install it or recompile php without --disable-hash' + ); + } + + if (!extension_loaded('iconv') && !extension_loaded('mbstring')) { + $errors['iconv_mbstring'] = array( + 'The iconv OR mbstring extension is required and both are missing.', + 'Install either of them or recompile php without --disable-iconv' + ); + } + + if (!ini_get('allow_url_fopen')) { + $errors['allow_url_fopen'] = array( + 'The allow_url_fopen setting is incorrect.', + 'Add the following to the end of your `php.ini`:', + ' allow_url_fopen = On', + $iniMessage + ); + } + + if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) { + $ioncube = ioncube_loader_version(); + $errors['ioncube'] = array( + 'Your ionCube Loader extension ('.$ioncube.') is incompatible with Phar files.', + 'Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:', + ' zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so', + $iniMessage + ); + } + + if (version_compare(PHP_VERSION, '5.3.2', '<')) { + $errors['php'] = array( + 'Your PHP ('.PHP_VERSION.') is too old, you must upgrade to PHP 5.3.2 or higher.' + ); + } + + if (version_compare(PHP_VERSION, '5.3.4', '<')) { + $warnings['php'] = array( + 'Your PHP ('.PHP_VERSION.') is quite old, upgrading to PHP 5.3.4 or higher is recommended.', + 'Composer works with 5.3.2+ for most people, but there might be edge case issues.' + ); + } + + if (!extension_loaded('openssl')) { + $warnings['openssl'] = array( + 'The openssl extension is missing, which means that secure HTTPS transfers are impossible.', + 'If possible you should enable it or recompile php with --with-openssl' + ); + } + + if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) { + // Attempt to parse version number out, fallback to whole string value. + $opensslVersion = trim(strstr(OPENSSL_VERSION_TEXT, ' ')); + $opensslVersion = substr($opensslVersion, 0, strpos($opensslVersion, ' ')); + $opensslVersion = $opensslVersion ? $opensslVersion : OPENSSL_VERSION_TEXT; + + $warnings['openssl_version'] = array( + 'The OpenSSL library ('.$opensslVersion.') used by PHP does not support TLSv1.2 or TLSv1.1.', + 'If possible you should upgrade OpenSSL to version 1.0.1 or above.' + ); + } + + if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && ini_get('apc.enable_cli')) { + $warnings['apc_cli'] = array( + 'The apc.enable_cli setting is incorrect.', + 'Add the following to the end of your `php.ini`:', + ' apc.enable_cli = Off', + $iniMessage + ); + } + + if (!$install && extension_loaded('xdebug')) { + $warnings['xdebug_loaded'] = array( + 'The xdebug extension is loaded, this can slow down Composer a little.', + 'Disabling it when using Composer is recommended.' + ); + + if (ini_get('xdebug.profiler_enabled')) { + $warnings['xdebug_profile'] = array( + 'The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.', + 'Add the following to the end of your `php.ini` to disable it:', + ' xdebug.profiler_enabled = 0', + $iniMessage + ); + } + } + + if (!extension_loaded('zlib')) { + $warnings['zlib'] = array( + 'The zlib extension is not loaded, this can slow down Composer a lot.', + 'If possible, install it or recompile php with --with-zlib', + $iniMessage + ); + } + + if (defined('PHP_WINDOWS_VERSION_BUILD') + && (version_compare(PHP_VERSION, '7.2.23', '<') + || (version_compare(PHP_VERSION, '7.3.0', '>=') + && version_compare(PHP_VERSION, '7.3.10', '<')))) { + $warnings['onedrive'] = array( + 'The Windows OneDrive folder is not supported on PHP versions below 7.2.23 and 7.3.10.', + 'Upgrade your PHP ('.PHP_VERSION.') to use this location with Composer.' + ); + } + + if (extension_loaded('uopz') && !(ini_get('uopz.disable') || ini_get('uopz.exit'))) { + $warnings['uopz'] = array( + 'The uopz extension ignores exit calls and may not work with all Composer commands.', + 'Disabling it when using Composer is recommended.' + ); + } + + ob_start(); + phpinfo(INFO_GENERAL); + $phpinfo = (string) ob_get_clean(); + if (preg_match('{Configure Command(?: *| *=> *)(.*?)(?:|$)}m', $phpinfo, $match)) { + $configure = $match[1]; + + if (false !== strpos($configure, '--enable-sigchild')) { + $warnings['sigchild'] = array( + 'PHP was compiled with --enable-sigchild which can cause issues on some platforms.', + 'Recompile it without this flag if possible, see also:', + ' https://bugs.php.net/bug.php?id=22999' + ); + } + + if (false !== strpos($configure, '--with-curlwrappers')) { + $warnings['curlwrappers'] = array( + 'PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.', + 'Recompile it without this flag if possible' + ); + } + } + + // Stringify the message arrays + foreach ($errors as $key => $value) { + $errors[$key] = PHP_EOL.implode(PHP_EOL, $value); + } + + foreach ($warnings as $key => $value) { + $warnings[$key] = PHP_EOL.implode(PHP_EOL, $value); + } + + return !empty($errors) || !empty($warnings); +} + + +/** + * Outputs an array of issues + * + * @param array $issues + */ +function outputIssues($issues) +{ + foreach ($issues as $issue) { + out($issue, 'info'); + } + out(''); +} + +/** + * Outputs any warnings found + * + * @param array $warnings + */ +function showWarnings($warnings) +{ + if (!empty($warnings)) { + out('Some settings on your machine may cause stability issues with Composer.', 'error'); + out('If you encounter issues, try to change the following:', 'error'); + outputIssues($warnings); + } +} + +/** + * Outputs an end of process warning if tls has been bypassed + * + * @param bool $disableTls Bypass tls + */ +function showSecurityWarning($disableTls) +{ + if ($disableTls) { + out('You have instructed the Installer not to enforce SSL/TLS security on remote HTTPS requests.', 'info'); + out('This will leave all downloads during installation vulnerable to Man-In-The-Middle (MITM) attacks', 'info'); + } +} + +/** + * colorize output + */ +function out($text, $color = null, $newLine = true) +{ + $styles = array( + 'success' => "\033[0;32m%s\033[0m", + 'error' => "\033[31;31m%s\033[0m", + 'info' => "\033[33;33m%s\033[0m" + ); + + $format = '%s'; + + if (is_string($color) && isset($styles[$color]) && USE_ANSI) { + $format = $styles[$color]; + } + + if ($newLine) { + $format .= PHP_EOL; + } + + printf($format, $text); +} + +/** + * Returns the system-dependent Composer home location, which may not exist + * + * @return string + */ +function getHomeDir() +{ + $home = getenv('COMPOSER_HOME'); + if ($home) { + return $home; + } + + $userDir = getUserDir(); + + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + return $userDir.'/Composer'; + } + + $dirs = array(); + + if (useXdg()) { + // XDG Base Directory Specifications + $xdgConfig = getenv('XDG_CONFIG_HOME'); + if (!$xdgConfig) { + $xdgConfig = $userDir . '/.config'; + } + + $dirs[] = $xdgConfig . '/composer'; + } + + $dirs[] = $userDir . '/.composer'; + + // select first dir which exists of: $XDG_CONFIG_HOME/composer or ~/.composer + foreach ($dirs as $dir) { + if (is_dir($dir)) { + return $dir; + } + } + + // if none exists, we default to first defined one (XDG one if system uses it, or ~/.composer otherwise) + return $dirs[0]; +} + +/** + * Returns the location of the user directory from the environment + * @throws RuntimeException If the environment value does not exists + * + * @return string + */ +function getUserDir() +{ + $userEnv = defined('PHP_WINDOWS_VERSION_MAJOR') ? 'APPDATA' : 'HOME'; + $userDir = getenv($userEnv); + + if (!$userDir) { + throw new RuntimeException('The '.$userEnv.' or COMPOSER_HOME environment variable must be set for composer to run correctly'); + } + + return rtrim(strtr($userDir, '\\', '/'), '/'); +} + +/** + * @return bool + */ +function useXdg() +{ + foreach (array_keys($_SERVER) as $key) { + if (strpos((string) $key, 'XDG_') === 0) { + return true; + } + } + + if (is_dir('/etc/xdg')) { + return true; + } + + return false; +} + +function validateCaFile($contents) +{ + // assume the CA is valid if php is vulnerable to + // https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html + if ( + PHP_VERSION_ID <= 50327 + || (PHP_VERSION_ID >= 50400 && PHP_VERSION_ID < 50422) + || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID < 50506) + ) { + return !empty($contents); + } + + return (bool) openssl_x509_parse($contents); +} + +/** + * Returns php.ini location information + * + * @return string + */ +function getIniMessage() +{ + $paths = array((string) php_ini_loaded_file()); + $scanned = php_ini_scanned_files(); + + if ($scanned !== false) { + $paths = array_merge($paths, array_map('trim', explode(',', $scanned))); + } + + // We will have at least one value, which may be empty + if ($paths[0] === '') { + array_shift($paths); + } + + $ini = array_shift($paths); + + if ($ini === null) { + return 'A php.ini file does not exist. You will have to create one.'; + } + + if (count($paths) > 1) { + return 'Your command-line PHP is using multiple ini files. Run `php --ini` to show them.'; + } + + return 'The php.ini used by your command-line PHP is: '.$ini; +} + +class Installer +{ + private $quiet; + private $disableTls; + private $cafile; + private $displayPath; + private $target; + private $tmpFile; + private $tmpCafile; + private $baseUrl; + private $algo; + private $errHandler; + private $httpClient; + private $pubKeys = array(); + private $installs = array(); + + /** + * Constructor - must not do anything that throws an exception + * + * @param bool $quiet Quiet mode + * @param bool $disableTls Bypass tls + * @param mixed $cafile Path to CA bundle, or false + */ + public function __construct($quiet, $disableTls, $caFile) + { + if (($this->quiet = $quiet)) { + ob_start(); + } + $this->disableTls = $disableTls; + $this->cafile = $caFile; + $this->errHandler = new ErrorHandler(); + } + + /** + * Runs the installer + * + * @param mixed $version Specific version to install, or false + * @param mixed $installDir Specific installation directory, or false + * @param string $filename Specific filename to save to, or composer.phar + * @param string $channel Specific version channel to use + * @throws Exception If anything other than a RuntimeException is caught + * + * @return bool If the installation succeeded + */ + public function run($version, $installDir, $filename, $channel) + { + try { + $this->initTargets($installDir, $filename); + $this->initTls(); + $this->httpClient = new HttpClient($this->disableTls, $this->cafile); + $result = $this->install($version, $channel); + + // in case --1 or --2 is passed, we leave the default channel for next self-update to stable + if (1 === preg_match('{^\d+$}D', $channel)) { + $channel = 'stable'; + } + + if ($result && $channel !== 'stable' && !$version && defined('PHP_BINARY')) { + $null = (defined('PHP_WINDOWS_VERSION_MAJOR') ? 'NUL' : '/dev/null'); + @exec(escapeshellarg(PHP_BINARY) .' '.escapeshellarg($this->target).' self-update --'.$channel.' --set-channel-only -q > '.$null.' 2> '.$null, $output); + } + } catch (Exception $e) { + $result = false; + } + + // Always clean up + $this->cleanUp($result); + + if (isset($e)) { + // Rethrow anything that is not a RuntimeException + if (!$e instanceof RuntimeException) { + throw $e; + } + out($e->getMessage(), 'error'); + } + return $result; + } + + /** + * Initialization methods to set the required filenames and composer url + * + * @param mixed $installDir Specific installation directory, or false + * @param string $filename Specific filename to save to, or composer.phar + * @throws RuntimeException If the installation directory is not writable + */ + protected function initTargets($installDir, $filename) + { + $this->displayPath = ($installDir ? rtrim($installDir, '/').'/' : '').$filename; + $installDir = $installDir ? realpath($installDir) : getcwd(); + + if (!is_writeable($installDir)) { + throw new RuntimeException('The installation directory "'.$installDir.'" is not writable'); + } + + $this->target = $installDir.DIRECTORY_SEPARATOR.$filename; + $this->tmpFile = $installDir.DIRECTORY_SEPARATOR.basename($this->target, '.phar').'-temp.phar'; + + $uriScheme = $this->disableTls ? 'http' : 'https'; + $this->baseUrl = $uriScheme.'://getcomposer.org'; + } + + /** + * A wrapper around methods to check tls and write public keys + * @throws RuntimeException If SHA384 is not supported + */ + protected function initTls() + { + if ($this->disableTls) { + return; + } + + if (!in_array('sha384', array_map('strtolower', openssl_get_md_methods()))) { + throw new RuntimeException('SHA384 is not supported by your openssl extension'); + } + + $this->algo = defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'SHA384'; + $home = $this->getComposerHome(); + + $this->pubKeys = array( + 'dev' => $this->installKey(self::getPKDev(), $home, 'keys.dev.pub'), + 'tags' => $this->installKey(self::getPKTags(), $home, 'keys.tags.pub') + ); + + if (empty($this->cafile) && !HttpClient::getSystemCaRootBundlePath()) { + $this->cafile = $this->tmpCafile = $this->installKey(HttpClient::getPackagedCaFile(), $home, 'cacert-temp.pem'); + } + } + + /** + * Returns the Composer home directory, creating it if required + * @throws RuntimeException If the directory cannot be created + * + * @return string + */ + protected function getComposerHome() + { + $home = getHomeDir(); + + if (!is_dir($home)) { + $this->errHandler->start(); + + if (!mkdir($home, 0777, true)) { + throw new RuntimeException(sprintf( + 'Unable to create Composer home directory "%s": %s', + $home, + $this->errHandler->message + )); + } + $this->installs[] = $home; + $this->errHandler->stop(); + } + return $home; + } + + /** + * Writes public key data to disc + * + * @param string $data The public key(s) in pem format + * @param string $path The directory to write to + * @param string $filename The name of the file + * @throws RuntimeException If the file cannot be written + * + * @return string The path to the saved data + */ + protected function installKey($data, $path, $filename) + { + $this->errHandler->start(); + + $target = $path.DIRECTORY_SEPARATOR.$filename; + $installed = file_exists($target); + $write = file_put_contents($target, $data, LOCK_EX); + @chmod($target, 0644); + + $this->errHandler->stop(); + + if (!$write) { + throw new RuntimeException(sprintf('Unable to write %s to: %s', $filename, $path)); + } + + if (!$installed) { + $this->installs[] = $target; + } + + return $target; + } + + /** + * The main install function + * + * @param mixed $version Specific version to install, or false + * @param string $channel Version channel to use + * + * @return bool If the installation succeeded + */ + protected function install($version, $channel) + { + $retries = 3; + $result = false; + $infoMsg = 'Downloading...'; + $infoType = 'info'; + + while ($retries--) { + if (!$this->quiet) { + out($infoMsg, $infoType); + $infoMsg = 'Retrying...'; + $infoType = 'error'; + } + + if (!$this->getVersion($channel, $version, $url, $error)) { + out($error, 'error'); + continue; + } + + if (!$this->downloadToTmp($url, $signature, $error)) { + out($error, 'error'); + continue; + } + + if (!$this->verifyAndSave($version, $signature, $error)) { + out($error, 'error'); + continue; + } + + $result = true; + break; + } + + if (!$this->quiet) { + if ($result) { + out(PHP_EOL."Composer (version {$version}) successfully installed to: {$this->target}", 'success'); + out("Use it: php {$this->displayPath}", 'info'); + out(''); + } else { + out('The download failed repeatedly, aborting.', 'error'); + } + } + return $result; + } + + /** + * Sets the version url, downloading version data if required + * + * @param string $channel Version channel to use + * @param false|string $version Version to install, or set by method + * @param null|string $url The versioned url, set by method + * @param null|string $error Set by method on failure + * + * @return bool If the operation succeeded + */ + protected function getVersion($channel, &$version, &$url, &$error) + { + $error = ''; + + if ($version) { + if (empty($url)) { + $url = $this->baseUrl."/download/{$version}/composer.phar"; + } + return true; + } + + $this->errHandler->start(); + + if ($this->downloadVersionData($data, $error)) { + $this->parseVersionData($data, $channel, $version, $url); + } + + $this->errHandler->stop(); + return empty($error); + } + + /** + * Downloads and json-decodes version data + * + * @param null|array $data Downloaded version data, set by method + * @param null|string $error Set by method on failure + * + * @return bool If the operation succeeded + */ + protected function downloadVersionData(&$data, &$error) + { + $url = $this->baseUrl.'/versions'; + $errFmt = 'The "%s" file could not be %s: %s'; + + if (!$json = $this->httpClient->get($url)) { + $error = sprintf($errFmt, $url, 'downloaded', $this->errHandler->message); + return false; + } + + if (!$data = json_decode($json, true)) { + $error = sprintf($errFmt, $url, 'json-decoded', $this->getJsonError()); + return false; + } + return true; + } + + /** + * A wrapper around the methods needed to download and save the phar + * + * @param string $url The versioned download url + * @param null|string $signature Set by method on successful download + * @param null|string $error Set by method on failure + * + * @return bool If the operation succeeded + */ + protected function downloadToTmp($url, &$signature, &$error) + { + $error = ''; + $errFmt = 'The "%s" file could not be downloaded: %s'; + $sigUrl = $url.'.sig'; + $this->errHandler->start(); + + if (!$fh = fopen($this->tmpFile, 'w')) { + $error = sprintf('Could not create file "%s": %s', $this->tmpFile, $this->errHandler->message); + + } elseif (!$this->getSignature($sigUrl, $signature)) { + $error = sprintf($errFmt, $sigUrl, $this->errHandler->message); + + } elseif (!fwrite($fh, $this->httpClient->get($url))) { + $error = sprintf($errFmt, $url, $this->errHandler->message); + } + + if (is_resource($fh)) { + fclose($fh); + } + $this->errHandler->stop(); + return empty($error); + } + + /** + * Verifies the downloaded file and saves it to the target location + * + * @param string $version The composer version downloaded + * @param string $signature The digital signature to check + * @param null|string $error Set by method on failure + * + * @return bool If the operation succeeded + */ + protected function verifyAndSave($version, $signature, &$error) + { + $error = ''; + + if (!$this->validatePhar($this->tmpFile, $pharError)) { + $error = 'The download is corrupt: '.$pharError; + + } elseif (!$this->verifySignature($version, $signature, $this->tmpFile)) { + $error = 'Signature mismatch, could not verify the phar file integrity'; + + } else { + $this->errHandler->start(); + + if (!rename($this->tmpFile, $this->target)) { + $error = sprintf('Could not write to file "%s": %s', $this->target, $this->errHandler->message); + } + chmod($this->target, 0755); + $this->errHandler->stop(); + } + + return empty($error); + } + + /** + * Parses an array of version data to match the required channel + * + * @param array $data Downloaded version data + * @param mixed $channel Version channel to use + * @param false|string $version Set by method + * @param mixed $url The versioned url, set by method + */ + protected function parseVersionData(array $data, $channel, &$version, &$url) + { + foreach ($data[$channel] as $candidate) { + if ($candidate['min-php'] <= PHP_VERSION_ID) { + $version = $candidate['version']; + $url = $this->baseUrl.$candidate['path']; + break; + } + } + + if (!$version) { + $error = sprintf( + 'None of the %d %s version(s) of Composer matches your PHP version (%s / ID: %d)', + count($data[$channel]), + $channel, + PHP_VERSION, + PHP_VERSION_ID + ); + throw new RuntimeException($error); + } + } + + /** + * Downloads the digital signature of required phar file + * + * @param string $url The signature url + * @param null|string $signature Set by method on success + * + * @return bool If the download succeeded + */ + protected function getSignature($url, &$signature) + { + if (!$result = $this->disableTls) { + $signature = $this->httpClient->get($url); + + if ($signature) { + $signature = json_decode($signature, true); + $signature = base64_decode($signature['sha384']); + $result = true; + } + } + + return $result; + } + + /** + * Verifies the signature of the downloaded phar + * + * @param string $version The composer versione + * @param string $signature The downloaded digital signature + * @param string $file The temp phar file + * + * @return bool If the operation succeeded + */ + protected function verifySignature($version, $signature, $file) + { + if (!$result = $this->disableTls) { + $path = preg_match('{^[0-9a-f]{40}$}', $version) ? $this->pubKeys['dev'] : $this->pubKeys['tags']; + $pubkeyid = openssl_pkey_get_public('file://'.$path); + + $result = 1 === openssl_verify( + file_get_contents($file), + $signature, + $pubkeyid, + $this->algo + ); + + // PHP 8 automatically frees the key instance and deprecates the function + if (PHP_VERSION_ID < 80000) { + openssl_free_key($pubkeyid); + } + } + + return $result; + } + + /** + * Validates the downloaded phar file + * + * @param string $pharFile The temp phar file + * @param null|string $error Set by method on failure + * + * @return bool If the operation succeeded + */ + protected function validatePhar($pharFile, &$error) + { + if (ini_get('phar.readonly')) { + return true; + } + + try { + // Test the phar validity + $phar = new Phar($pharFile); + // Free the variable to unlock the file + unset($phar); + $result = true; + + } catch (Exception $e) { + if (!$e instanceof UnexpectedValueException && !$e instanceof PharException) { + throw $e; + } + $error = $e->getMessage(); + $result = false; + } + return $result; + } + + /** + * Returns a string representation of the last json error + * + * @return string The error string or code + */ + protected function getJsonError() + { + if (function_exists('json_last_error_msg')) { + return json_last_error_msg(); + } else { + return 'json_last_error = '.json_last_error(); + } + } + + /** + * Cleans up resources at the end of the installation + * + * @param bool $result If the installation succeeded + */ + protected function cleanUp($result) + { + if ($this->quiet) { + // Ensure output buffers are emptied + $errors = explode(PHP_EOL, (string) ob_get_clean()); + } + + if (!$result) { + // Output buffered errors + if ($this->quiet) { + $this->outputErrors($errors); + } + // Clean up stuff we created + $this->uninstall(); + } elseif ($this->tmpCafile !== null) { + @unlink($this->tmpCafile); + } + } + + /** + * Outputs unique errors when in quiet mode + * + */ + protected function outputErrors(array $errors) + { + $shown = array(); + + foreach ($errors as $error) { + if ($error && !in_array($error, $shown)) { + out($error, 'error'); + $shown[] = $error; + } + } + } + + /** + * Uninstalls newly-created files and directories on failure + * + */ + protected function uninstall() + { + foreach (array_reverse($this->installs) as $target) { + if (is_file($target)) { + @unlink($target); + } elseif (is_dir($target)) { + @rmdir($target); + } + } + + if ($this->tmpFile !== null && file_exists($this->tmpFile)) { + @unlink($this->tmpFile); + } + } + + public static function getPKDev() + { + return <<message) { + $this->message .= PHP_EOL; + } + $this->message .= preg_replace('{^file_get_contents\(.*?\): }', '', $msg); + } + + /** + * Starts error-handling if not already active + * + * Any message is cleared + */ + public function start() + { + if (!$this->active) { + set_error_handler(array($this, 'handleError')); + $this->active = true; + } + $this->message = ''; + } + + /** + * Stops error-handling if active + * + * Any message is preserved until the next call to start() + */ + public function stop() + { + if ($this->active) { + restore_error_handler(); + $this->active = false; + } + } +} + +class NoProxyPattern +{ + private $composerInNoProxy = false; + private $rulePorts = array(); + + public function __construct($pattern) + { + $rules = preg_split('{[\s,]+}', $pattern, null, PREG_SPLIT_NO_EMPTY); + + if ($matches = preg_grep('{getcomposer\.org(?::\d+)?}i', $rules)) { + $this->composerInNoProxy = true; + + foreach ($matches as $match) { + if (strpos($match, ':') !== false) { + list(, $port) = explode(':', $match); + $this->rulePorts[] = (int) $port; + } + } + } + } + + /** + * Returns true if NO_PROXY contains getcomposer.org + * + * @param string $url http(s)://getcomposer.org + * + * @return bool + */ + public function test($url) + { + if (!$this->composerInNoProxy) { + return false; + } + + if (empty($this->rulePorts)) { + return true; + } + + if (strpos($url, 'http://') === 0) { + $port = 80; + } else { + $port = 443; + } + + return in_array($port, $this->rulePorts); + } +} + +class HttpClient { + + /** @var null|string */ + private static $caPath; + + private $options = array('http' => array()); + private $disableTls = false; + + public function __construct($disableTls = false, $cafile = false) + { + $this->disableTls = $disableTls; + if ($this->disableTls === false) { + if (!empty($cafile) && !is_dir($cafile)) { + if (!is_readable($cafile) || !validateCaFile(file_get_contents($cafile))) { + throw new RuntimeException('The configured cafile (' .$cafile. ') was not valid or could not be read.'); + } + } + $options = $this->getTlsStreamContextDefaults($cafile); + $this->options = array_replace_recursive($this->options, $options); + } + } + + public function get($url) + { + if (function_exists('http_clear_last_response_headers')) { + $http_response_header = http_clear_last_response_headers(); + } + + $context = $this->getStreamContext($url); + $result = file_get_contents($url, false, $context); + + if ($result && extension_loaded('zlib')) { + if (function_exists('http_get_last_response_headers')) { + $http_response_header = http_get_last_response_headers(); + } + $headers = $http_response_header; + $decode = false; + foreach ($headers as $header) { + if (preg_match('{^content-encoding: *gzip *$}i', $header)) { + $decode = true; + continue; + } elseif (preg_match('{^HTTP/}i', $header)) { + $decode = false; + } + } + + if ($decode) { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + $result = zlib_decode($result); + } else { + // work around issue with gzuncompress & co that do not work with all gzip checksums + $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result)); + } + + if (!$result) { + throw new RuntimeException('Failed to decode zlib stream'); + } + } + } + + return $result; + } + + protected function getStreamContext($url) + { + if ($this->disableTls === false) { + if (PHP_VERSION_ID < 50600) { + $this->options['ssl']['SNI_server_name'] = parse_url($url, PHP_URL_HOST); + } + } + // Keeping the above mostly isolated from the code copied from Composer. + return $this->getMergedStreamContext($url); + } + + protected function getTlsStreamContextDefaults($cafile) + { + $ciphers = implode(':', array( + 'ECDHE-RSA-AES128-GCM-SHA256', + 'ECDHE-ECDSA-AES128-GCM-SHA256', + 'ECDHE-RSA-AES256-GCM-SHA384', + 'ECDHE-ECDSA-AES256-GCM-SHA384', + 'DHE-RSA-AES128-GCM-SHA256', + 'DHE-DSS-AES128-GCM-SHA256', + 'kEDH+AESGCM', + 'ECDHE-RSA-AES128-SHA256', + 'ECDHE-ECDSA-AES128-SHA256', + 'ECDHE-RSA-AES128-SHA', + 'ECDHE-ECDSA-AES128-SHA', + 'ECDHE-RSA-AES256-SHA384', + 'ECDHE-ECDSA-AES256-SHA384', + 'ECDHE-RSA-AES256-SHA', + 'ECDHE-ECDSA-AES256-SHA', + 'DHE-RSA-AES128-SHA256', + 'DHE-RSA-AES128-SHA', + 'DHE-DSS-AES128-SHA256', + 'DHE-RSA-AES256-SHA256', + 'DHE-DSS-AES256-SHA', + 'DHE-RSA-AES256-SHA', + 'AES128-GCM-SHA256', + 'AES256-GCM-SHA384', + 'AES128-SHA256', + 'AES256-SHA256', + 'AES128-SHA', + 'AES256-SHA', + 'AES', + 'CAMELLIA', + 'DES-CBC3-SHA', + '!aNULL', + '!eNULL', + '!EXPORT', + '!DES', + '!RC4', + '!MD5', + '!PSK', + '!aECDH', + '!EDH-DSS-DES-CBC3-SHA', + '!EDH-RSA-DES-CBC3-SHA', + '!KRB5-DES-CBC3-SHA', + )); + + /** + * CN_match and SNI_server_name are only known once a URL is passed. + * They will be set in the getOptionsForUrl() method which receives a URL. + * + * cafile or capath can be overridden by passing in those options to constructor. + */ + $options = array( + 'ssl' => array( + 'ciphers' => $ciphers, + 'verify_peer' => true, + 'verify_depth' => 7, + 'SNI_enabled' => true, + ) + ); + + /** + * Attempt to find a local cafile or throw an exception. + * The user may go download one if this occurs. + */ + if (!$cafile) { + $cafile = self::getSystemCaRootBundlePath(); + } + if (is_dir($cafile)) { + $options['ssl']['capath'] = $cafile; + } elseif ($cafile) { + $options['ssl']['cafile'] = $cafile; + } else { + throw new RuntimeException('A valid cafile could not be located automatically.'); + } + + /** + * Disable TLS compression to prevent CRIME attacks where supported. + */ + if (version_compare(PHP_VERSION, '5.4.13') >= 0) { + $options['ssl']['disable_compression'] = true; + } + + return $options; + } + + /** + * function copied from Composer\Util\StreamContextFactory::initOptions + * + * Any changes should be applied there as well, or backported here. + * + * @param string $url URL the context is to be used for + * @return resource Default context + * @throws \RuntimeException if https proxy required and OpenSSL uninstalled + */ + protected function getMergedStreamContext($url) + { + $options = $this->options; + + // Handle HTTP_PROXY/http_proxy on CLI only for security reasons + if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) { + $proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']); + } + + // Prefer CGI_HTTP_PROXY if available + if (!empty($_SERVER['CGI_HTTP_PROXY'])) { + $proxy = parse_url($_SERVER['CGI_HTTP_PROXY']); + } + + // Override with HTTPS proxy if present and URL is https + if (preg_match('{^https://}i', $url) && (!empty($_SERVER['HTTPS_PROXY']) || !empty($_SERVER['https_proxy']))) { + $proxy = parse_url(!empty($_SERVER['https_proxy']) ? $_SERVER['https_proxy'] : $_SERVER['HTTPS_PROXY']); + } + + // Remove proxy if URL matches no_proxy directive + if (!empty($_SERVER['NO_PROXY']) || !empty($_SERVER['no_proxy']) && parse_url($url, PHP_URL_HOST)) { + $pattern = new NoProxyPattern(!empty($_SERVER['no_proxy']) ? $_SERVER['no_proxy'] : $_SERVER['NO_PROXY']); + if ($pattern->test($url)) { + unset($proxy); + } + } + + if (!empty($proxy)) { + $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : ''; + $proxyURL .= isset($proxy['host']) ? $proxy['host'] : ''; + + if (isset($proxy['port'])) { + $proxyURL .= ":" . $proxy['port']; + } elseif (strpos($proxyURL, 'http://') === 0) { + $proxyURL .= ":80"; + } elseif (strpos($proxyURL, 'https://') === 0) { + $proxyURL .= ":443"; + } + + // check for a secure proxy + if (strpos($proxyURL, 'https://') === 0) { + if (!extension_loaded('openssl')) { + throw new RuntimeException('You must enable the openssl extension to use a secure proxy.'); + } + if (strpos($url, 'https://') === 0) { + throw new RuntimeException('PHP does not support https requests through a secure proxy.'); + } + } + + // http(s):// is not supported in proxy + $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL); + + $options['http'] = array( + 'proxy' => $proxyURL, + ); + + // add request_fulluri for http requests + if ('http' === parse_url($url, PHP_URL_SCHEME)) { + $options['http']['request_fulluri'] = true; + } + + // handle proxy auth if present + if (isset($proxy['user'])) { + $auth = rawurldecode($proxy['user']); + if (isset($proxy['pass'])) { + $auth .= ':' . rawurldecode($proxy['pass']); + } + $auth = base64_encode($auth); + + $options['http']['header'] = "Proxy-Authorization: Basic {$auth}\r\n"; + } + } + + if (isset($options['http']['header'])) { + $options['http']['header'] .= "Connection: close\r\n"; + } else { + $options['http']['header'] = "Connection: close\r\n"; + } + if (extension_loaded('zlib')) { + $options['http']['header'] .= "Accept-Encoding: gzip\r\n"; + } + $options['http']['header'] .= "User-Agent: ".COMPOSER_INSTALLER."\r\n"; + $options['http']['protocol_version'] = 1.1; + $options['http']['timeout'] = 600; + + return stream_context_create($options); + } + + /** + * This method was adapted from Sslurp. + * https://github.com/EvanDotPro/Sslurp + * + * (c) Evan Coury + * + * For the full copyright and license information, please see below: + * + * Copyright (c) 2013, Evan Coury + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + public static function getSystemCaRootBundlePath() + { + if (self::$caPath !== null) { + return self::$caPath; + } + + // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that. + // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. + $envCertFile = getenv('SSL_CERT_FILE'); + if ($envCertFile && is_readable($envCertFile) && validateCaFile(file_get_contents($envCertFile))) { + return self::$caPath = $envCertFile; + } + + // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that. + // This mimics how OpenSSL uses the SSL_CERT_FILE env variable. + $envCertDir = getenv('SSL_CERT_DIR'); + if ($envCertDir && is_dir($envCertDir) && is_readable($envCertDir)) { + return self::$caPath = $envCertDir; + } + + $configured = ini_get('openssl.cafile'); + if ($configured && strlen($configured) > 0 && is_readable($configured) && validateCaFile(file_get_contents($configured))) { + return self::$caPath = $configured; + } + + $configured = ini_get('openssl.capath'); + if ($configured && is_dir($configured) && is_readable($configured)) { + return self::$caPath = $configured; + } + + $caBundlePaths = array( + '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package) + '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package) + '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package) + '/usr/local/share/certs/ca-root-nss.crt', // FreeBSD (ca_root_nss_package) + '/usr/ssl/certs/ca-bundle.crt', // Cygwin + '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package + '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option) + '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat? + '/etc/ssl/cert.pem', // OpenBSD + '/usr/local/etc/ssl/cert.pem', // FreeBSD 10.x + '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package + '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package + '/opt/homebrew/etc/openssl@3/cert.pem', // macOS silicon homebrew, openssl@3 package + '/opt/homebrew/etc/openssl@1.1/cert.pem', // macOS silicon homebrew, openssl@1.1 package + ); + + foreach ($caBundlePaths as $caBundle) { + if (@is_readable($caBundle) && validateCaFile(file_get_contents($caBundle))) { + return self::$caPath = $caBundle; + } + } + + foreach ($caBundlePaths as $caBundle) { + $caBundle = dirname($caBundle); + if (is_dir($caBundle) && glob($caBundle.'/*')) { + return self::$caPath = $caBundle; + } + } + + return self::$caPath = false; + } + + public static function getPackagedCaFile() + { + return <<fetchAll(); ?>
-

سجل الحضور والانصراف

+

سجل الحضور والانصراف ربط البصمة ZKTeco

diff --git a/hr_employees.php b/hr_employees.php index 342a605..d49bfcd 100644 --- a/hr_employees.php +++ b/hr_employees.php @@ -19,7 +19,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $error = "لا تملك صلاحية التعديل."; } else { $id = !empty($_POST['id']) ? $_POST['id'] : null; - $first_name = trim($_POST['first_name']); + $first_name = trim($_POST["first_name"]); + $zkteco_uid = !empty($_POST["zkteco_uid"]) ? trim($_POST["zkteco_uid"]) : null; $last_name = trim($_POST['last_name']); $email = trim($_POST['email']); $phone = trim($_POST['phone']); @@ -37,13 +38,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { if ($id) { // Update - $stmt = db()->prepare("UPDATE hr_employees SET first_name=?, last_name=?, email=?, phone=?, department_id=?, job_title=?, basic_salary=?, join_date=?, status=?, gender=?, birth_date=? WHERE id=?"); - $stmt->execute([$first_name, $last_name, $email, $phone, $department_id, $job_title, $basic_salary, $join_date, $status, $gender, $birth_date, $id]); + $stmt = db()->prepare("UPDATE hr_employees SET first_name=?, last_name=?, email=?, phone=?, department_id=?, job_title=?, basic_salary=?, join_date=?, status=?, gender=?, birth_date=?, zkteco_uid=? WHERE id=?"); + $stmt->execute([$first_name, $last_name, $email, $phone, $department_id, $job_title, $basic_salary, $join_date, $status, $gender, $birth_date, $zkteco_uid, $id]); $success = "تم تحديث بيانات الموظف بنجاح."; } else { // Insert - $stmt = db()->prepare("INSERT INTO hr_employees (first_name, last_name, email, phone, department_id, job_title, basic_salary, join_date, status, gender, birth_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute([$first_name, $last_name, $email, $phone, $department_id, $job_title, $basic_salary, $join_date, $status, $gender, $birth_date]); + $stmt = db()->prepare("INSERT INTO hr_employees (first_name, last_name, email, phone, department_id, job_title, basic_salary, join_date, status, gender, birth_date, zkteco_uid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$first_name, $last_name, $email, $phone, $department_id, $job_title, $basic_salary, $join_date, $status, $gender, $birth_date, $zkteco_uid]); $success = "تم إضافة الموظف بنجاح."; } } catch (PDOException $e) { @@ -207,7 +208,10 @@ $pagination = getPagination($page, $totalEmployees, $perPage);
- +
+
+ +
@@ -242,6 +246,7 @@ $pagination = getPagination($page, $totalEmployees, $perPage); المسمى الوظيفي تاريخ التعيين الحالة + بصمة (UID) الإجراءات @@ -286,6 +291,7 @@ $pagination = getPagination($page, $totalEmployees, $perPage); ?> +
@@ -346,7 +352,8 @@ $pagination = getPagination($page, $totalEmployees, $perPage); document.getElementById('empDept').value = btn.dataset.dept; document.getElementById('empJobTitle').value = btn.dataset.job; document.getElementById('empJoinDate').value = btn.dataset.join; - document.getElementById('empSalary').value = btn.dataset.salary; + document.getElementById("empSalary").value = btn.dataset.salary; + document.getElementById("empZktecoUid").value = btn.dataset.zkteco; document.getElementById('empStatus').value = btn.dataset.status; } diff --git a/hr_zkteco.php b/hr_zkteco.php new file mode 100644 index 0000000..135e5a4 --- /dev/null +++ b/hr_zkteco.php @@ -0,0 +1,283 @@ +ليس لديك صلاحية للوصول إلى هذه الصفحة.
"; + require_once 'includes/footer.php'; + exit; +} + +$error = ''; +$success = ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (isset($_POST['save_settings'])) { + $ip = trim($_POST['ip_address']); + $port = (int)$_POST['port']; + if ($ip && $port) { + $stmt = db()->prepare("UPDATE hr_zkteco_settings SET ip_address = ?, port = ? WHERE id = 1"); + $stmt->execute([$ip, $port]); + $success = "تم حفظ الإعدادات بنجاح."; + } else { + $error = "يرجى تعبئة الحقول المطلوبة."; + } + } + + $stmt = db()->query("SELECT * FROM hr_zkteco_settings LIMIT 1"); + $settings = $stmt->fetch(); + $ip = $settings['ip_address'] ?? '192.168.1.201'; + $port = $settings['port'] ?? 4370; + + if (isset($_POST['test_connection'])) { + try { + $zk = new ZKTeco($ip, $port); + if ($zk->connect()) { + $success = "تم الاتصال بالجهاز بنجاح (الإصدار: " . htmlspecialchars($zk->version() ?? 'غير معروف') . ")"; + $zk->disconnect(); + } else { + $error = "فشل الاتصال بالجهاز. يرجى التأكد من أن الجهاز على نفس الشبكة والمنفذ مفتوح (عادة 4370)."; + } + } catch (Exception $e) { + $error = "خطأ أثناء الاتصال: " . $e->getMessage(); + } + } + + if (isset($_POST['sync_logs'])) { + try { + $zk = new ZKTeco($ip, $port); + if ($zk->connect()) { + $attendance = $zk->getAttendance(); + + $empStmt = db()->query("SELECT id, zkteco_uid FROM hr_employees WHERE zkteco_uid IS NOT NULL AND zkteco_uid != '' AND status = 'active'"); + $empMap = []; + while ($emp = $empStmt->fetch()) { + $empMap[(string)$emp['zkteco_uid']] = $emp['id']; + } + + $syncedCount = 0; + if (is_array($attendance)) { + $attData = []; + foreach ($attendance as $att) { + $uid = (string)$att['id']; + if (isset($empMap[$uid])) { + $emp_id = $empMap[$uid]; + $timeStr = $att['timestamp']; + $date = date('Y-m-d', strtotime($timeStr)); + $time = date('H:i:s', strtotime($timeStr)); + + if (!isset($attData[$emp_id][$date])) { + $attData[$emp_id][$date] = ['min' => $time, 'max' => $time]; + } else { + if ($time < $attData[$emp_id][$date]['min']) $attData[$emp_id][$date]['min'] = $time; + if ($time > $attData[$emp_id][$date]['max']) $attData[$emp_id][$date]['max'] = $time; + } + } + } + + foreach ($attData as $emp_id => $dates) { + foreach ($dates as $date => $times) { + $check_in = $times['min']; + $check_out = ($times['max'] != $times['min']) ? $times['max'] : null; + + $chkStmt = db()->prepare("SELECT id, check_in, check_out FROM hr_attendance WHERE employee_id = ? AND date = ?"); + $chkStmt->execute([$emp_id, $date]); + $existing = $chkStmt->fetch(); + + if ($existing) { + $upd_in = $existing['check_in']; + $upd_out = $existing['check_out']; + $changed = false; + + if (empty($upd_in) || $check_in < $upd_in) { $upd_in = $check_in; $changed = true; } + if ($check_out && (empty($upd_out) || $check_out > $upd_out)) { $upd_out = $check_out; $changed = true; } + + if ($changed) { + $uStmt = db()->prepare("UPDATE hr_attendance SET check_in = ?, check_out = ?, status = 'present' WHERE id = ?"); + $uStmt->execute([$upd_in, $upd_out, $existing['id']]); + $syncedCount++; + } + } else { + $iStmt = db()->prepare("INSERT INTO hr_attendance (employee_id, date, check_in, check_out, status) VALUES (?, ?, ?, ?, 'present')"); + $iStmt->execute([$emp_id, $date, $check_in, $check_out]); + $syncedCount++; + } + } + } + } + + $zk->disconnect(); + if ($syncedCount > 0) { + $success = "تم مزامنة $syncedCount سجل حضور (تحديث / إنشاء) بنجاح."; + } else { + $success = "تم الاتصال بنجاح ولكن لم يتم العثور على سجلات جديدة للموظفين المعرفين."; + } + + } else { + $error = "فشل الاتصال بالجهاز."; + } + } catch (Exception $e) { + $error = "حدث خطأ أثناء المزامنة: " . $e->getMessage(); + } + } +} else { + $stmt = db()->query("SELECT * FROM hr_zkteco_settings LIMIT 1"); + $settings = $stmt->fetch(); + $ip = $settings['ip_address'] ?? '192.168.1.201'; + $port = $settings['port'] ?? 4370; +} +?> + +
+
+
+

ربط جهاز البصمة ZKTeco

+

اختر طريقة الربط المناسبة للشبكة الخاصة بك لسحب سجلات الحضور.

+
+ +
+ + + + + + + + + + + + + +
+ +
+
+ هذه الطريقة تتطلب أن يكون الخادم (السيرفر) قادراً على الوصول إلى عنوان IP الخاص بالجهاز. إذا كان الجهاز في شبكة محلية مختلفة ولا يوجد IP ثابت، استخدم الطريقة 2. +
+ +
+
+
+
+
إعدادات الاتصال المباشر
+
+
+ +
+ + +
+
+ + +
+
+ +
+ +
+
+
+ +
+
+
+
العمليات
+
+
+

اختبر الاتصال أو ابدأ بسحب البيانات الان.

+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
+
النظام جاهز لاستقبال البصمات تلقائياً (ADMS/WDMS)
+

من خلال هذه الطريقة، يقوم جهاز البصمة بإرسال سجلات الحضور تلقائياً إلى هذا النظام عبر الإنترنت. هذه الطريقة مثالية إذا كان جهاز البصمة في شبكة محلية بدون IP ثابت (Public IP).

+ +
خطوات الإعداد على جهاز البصمة ZKTeco:
+ +
+
+
    +
  • +
    +
    1. إعدادات الشبكة (Comm.)
    + افتح القائمة الرئيسية للجهاز واذهب إلى COMM. ثم Cloud Server Setting أو ADMS. +
    + +
  • +
  • +
    +
    2. عنوان السيرفر (Server Address)
    + أدخل عنوان هذا النظام أو الـ IP الخاص به. +
    مثال: إذا كان عنوان التطبيق http://your-app.com أدخل فقط your-app.com +
    +
  • +
  • +
    +
    3. منفذ السيرفر (Server Port)
    + أدخل المنفذ: 80 للاتصال العادي (أو 443 إذا كنت تستخدم HTTPS مدعوم). +
    +
  • +
  • +
    +
    4. تفعيل المزامنة
    + بمجرد الحفظ، سيبدأ الجهاز بإرسال بيانات الحضور تلقائياً إلى السيرفر. ستظهر السجلات في صفحة "سجل الحضور". +
    + +
  • +
+
+
+
+
+
خطوة هامة لربط الموظفين
+

لكي يتم احتساب البصمات للموظف الصحيح، تأكد من الذهاب إلى قائمة الموظفين وتعديل بيانات كل موظف وإضافة ZKTeco UID (رقم الموظف في جهاز البصمة).

+
+
+
+
+
+
+
+
+
+ + diff --git a/iclock.php b/iclock.php new file mode 100644 index 0000000..2958e22 --- /dev/null +++ b/iclock.php @@ -0,0 +1 @@ + diff --git a/iclock/cdata.php b/iclock/cdata.php new file mode 100644 index 0000000..ac6f1cc --- /dev/null +++ b/iclock/cdata.php @@ -0,0 +1 @@ + diff --git a/iclock/cdata/index.php b/iclock/cdata/index.php new file mode 100644 index 0000000..ac6f1cc --- /dev/null +++ b/iclock/cdata/index.php @@ -0,0 +1 @@ + diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..f6cbb7b --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,22 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..2052022 --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,396 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool + */ + private static $installedIsLocalDir; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + $copiedLocalDir = false; + + if (self::$canGetVendors) { + $selfDir = self::getSelfDir(); + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; + } + } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array() && !$copiedLocalDir) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..0fb0a2c --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,10 @@ + $vendorDir . '/composer/InstalledVersions.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/rats/zkteco/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..439c91d --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,36 @@ +register(true); + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..151e8da --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,36 @@ + + array ( + 'Rats\\Zkteco\\' => 12, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Rats\\Zkteco\\' => + array ( + 0 => __DIR__ . '/..' . '/rats/zkteco/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit6910b6f5ff1d98cde5c3ed7e3c06c3dd::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit6910b6f5ff1d98cde5c3ed7e3c06c3dd::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit6910b6f5ff1d98cde5c3ed7e3c06c3dd::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..451876f --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,46 @@ +{ + "packages": [ + { + "name": "rats/zkteco", + "version": "V002", + "version_normalized": "002.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/raihanafroz/zkteco.git", + "reference": "6e17024370ec56c89d87c85a54a1c55079304834" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/raihanafroz/zkteco/zipball/6e17024370ec56c89d87c85a54a1c55079304834", + "reference": "6e17024370ec56c89d87c85a54a1c55079304834", + "shasum": "" + }, + "time": "2021-11-21T17:53:28+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Rats\\Zkteco\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raihan Afroz", + "email": "raihanafroz9@gmail.com" + } + ], + "description": "ZKTeco Laravel Library", + "support": { + "issues": "https://github.com/raihanafroz/zkteco/issues", + "source": "https://github.com/raihanafroz/zkteco/tree/V002" + }, + "install-path": "../rats/zkteco" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..e07ee09 --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,32 @@ + array( + 'name' => '__root__', + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '79a5056e8ec090b0861aa5c8dc1189870f3fe8eb', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '79a5056e8ec090b0861aa5c8dc1189870f3fe8eb', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'rats/zkteco' => array( + 'pretty_version' => 'V002', + 'version' => '002.0.0.0', + 'reference' => '6e17024370ec56c89d87c85a54a1c55079304834', + 'type' => 'library', + 'install_path' => __DIR__ . '/../rats/zkteco', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/vendor/rats/zkteco/CHANGELOG.md b/vendor/rats/zkteco/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/vendor/rats/zkteco/README.md b/vendor/rats/zkteco/README.md new file mode 100644 index 0000000..7cbbcd9 --- /dev/null +++ b/vendor/rats/zkteco/README.md @@ -0,0 +1,292 @@ +# ZKTeco - Laravel Library # + +[![Issues](https://img.shields.io/github/issues/raihanafroz/zkteco?style=flat-square)](https://github.com/raihanafroz/zkteco/issues) +[![Forks](https://img.shields.io/github/forks/raihanafroz/zkteco?style=flat-square)](https://github.com/raihanafroz/zkteco/network/members) +[![Stars](https://img.shields.io/github/stars/raihanafroz/zkteco?style=flat-square)](https://github.com/raihanafroz/zkteco/stargazers) +[![Total Downloads](https://img.shields.io/packagist/dt/rats/zkteco?style=flat-square)](https://packagist.org/packages/rats/zkteco) +[![License](https://poser.pugx.org/rats/zkteco/license.svg)](https://packagist.org/packages/rats/zkteco) + + +The `rats/zkteco` package provides easy to use functions to ZKTeco Device activities. + +__Requires:__ **Laravel** >= **6.0** + +__License:__ MIT or later + +## Installation: +You can install the package via composer: + +``` bash +composer require rats/zkteco +``` +The package will automatically register itself. + +You have to enable your php socket if it is not enable. + + +## Usage + +1. Create a object of ZKTeco class. + +```php + use Rats\Zkteco\Lib\ZKTeco; + +// 1 s't parameter is string $ip Device IP Address +// 2 nd parameter is integer $port Default: 4370 + + $zk = new ZKTeco('192.168.1.201'); + +// or you can use with port +// $zk = new ZKTeco('192.168.1.201', 8080); + +``` + +2. Call ZKTeco methods + +* __Connect__ +```php +// connect +// this return bool + $zk->connect(); +``` + +* __Disconnect__ +```php +// disconnect +// this return bool + + $zk->disconnect(); +``` + +* __Enable Device__ +```php +// enable +// this return bool/mixed + + $zk->enableDevice(); +``` +> **NOTE**: You have to call after read/write any info of Device. + +* __Disable Device__ +```php +// disable +// this return bool/mixed + + $zk->disableDevice(); +``` +> **NOTE**: You have to call before read/write any info of Device. + + +* __Device Version__ +```php +// get device version +// this return bool/mixed + + $zk->version(); +``` + + +* __Device Os Version__ +```php +// get device os version +// this return bool/mixed + + $zk->osVersion(); +``` + +* __Power Off__ +```php +// turn off the device +// this return bool/mixed + + $zk->shutdown(); +``` + +* __Restart__ +```php +// restart the device +// this return bool/mixed + + $zk->restart(); +``` + +* __Sleep__ +```php +// sleep the device +// this return bool/mixed + + $zk->sleep(); +``` + +* __Resume__ +```php +// resume the device from sleep +// this return bool/mixed + + $zk->resume(); +``` + +* __Voice Test__ +```php +// voice test of the device "Thank you" +// this return bool/mixed + + $zk->testVoice(); +``` + +* __Platform__ +```php +// get platform +// this return bool/mixed + + $zk->platform(); +``` + +* __Firmware Version__ +```php +// get firmware version +// this return bool/mixed + + $zk->fmVersion(); +``` + +* __Work Code__ +```php +// get work code +// this return bool/mixed + + $zk->workCode(); +``` + +* __SSR__ +```php +// get SSR +// this return bool/mixed + + $zk->ssr(); +``` + +* __Pin Width__ +```php +// get Pin Width +// this return bool/mixed + + $zk->pinWidth(); +``` + +* __Serial Number__ +```php +// get device serial number +// this return bool/mixed + + $zk->serialNumber(); +``` + +* __Device Name__ +```php +// get device name +// this return bool/mixed + + $zk->deviceName(); +``` + +* __Get Device Time__ +```php +// get device time + +// return bool/mixed bool|mixed Format: "Y-m-d H:i:s" + + $zk->getTime(); +``` + +* __Set Device Time__ +```php +// set device time +// parameter string $t Format: "Y-m-d H:i:s" +// return bool/mixed + + $zk->setTime(); +``` + +* __Get Users__ +```php +// get User +// this return array[] + + $zk->getUser(); +``` + +* __Set Users__ +```php +// set user + +// 1 s't parameter int $uid Unique ID (max 65535) +// 2 nd parameter int|string $userid ID in DB (same like $uid, max length = 9, only numbers - depends device setting) +// 3 rd parameter string $name (max length = 24) +// 4 th parameter int|string $password (max length = 8, only numbers - depends device setting) +// 5 th parameter int $role Default Util::LEVEL_USER +// 6 th parameter int $cardno Default 0 (max length = 10, only numbers + +// return bool|mixed + + $zk->setUser(); +``` + +* __Clear All Admin__ +```php +// remove all admin +// return bool|mixed + + $zk->clearAdmin(); +``` + +* __Clear All Users__ +```php +// remove all users +// return bool|mixed + + $zk->clearAdmin(); +``` + +* __Remove A User__ +```php +// remove a user by $uid +// parameter integer $uid +// return bool|mixed + + $zk->removeUser(); +``` + +* __Get Attendance Log__ +```php +// get attendance log + +// return array[] + +// like as 0 => array:5 [▼ +// "uid" => 1 /* serial number of the attendance */ +// "id" => "1" /* user id of the application */ +// "state" => 1 /* the authentication type, 1 for Fingerprint, 4 for RF Card etc */ +// "timestamp" => "2020-05-27 21:21:06" /* time of attendance */ +// "type" => 255 /* attendance type, like check-in, check-out, overtime-in, overtime-out, break-in & break-out etc. if attendance type is none of them, it gives 255. */ +// ] + + $zk->getAttendance(); +``` + +* __Clear Attendance Log__ +```php +// clear attendance log + +// return bool/mixed + + $zk->clearAttendance(); +``` + + + + + + + +# end diff --git a/vendor/rats/zkteco/composer.json b/vendor/rats/zkteco/composer.json new file mode 100644 index 0000000..c0ba8d6 --- /dev/null +++ b/vendor/rats/zkteco/composer.json @@ -0,0 +1,19 @@ +{ + "name": "rats/zkteco", + "description": "ZKTeco Laravel Library", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Raihan Afroz", + "email": "raihanafroz9@gmail.com" + } + ], + "autoload": { + "psr-4":{ + "Rats\\Zkteco\\": "src" + } + }, + "minimum-stability": "dev", + "require": {} +} diff --git a/vendor/rats/zkteco/src/Lib/Helper/Attendance.php b/vendor/rats/zkteco/src/Lib/Helper/Attendance.php new file mode 100644 index 0000000..20ca99b --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/Attendance.php @@ -0,0 +1,72 @@ +_section = __METHOD__; + + $command = Util::CMD_ATT_LOG_RRQ; + $command_string = ''; + + $session = $self->_command($command, $command_string, Util::COMMAND_TYPE_DATA); + if ($session === false) { + return []; + } + + $attData = Util::recData($self); + + $attendance = []; + if (!empty($attData)) { + $attData = substr($attData, 10); + + while (strlen($attData) > 40) { + $u = unpack('H78', substr($attData, 0, 39)); + + $u1 = hexdec(substr($u[1], 4, 2)); + $u2 = hexdec(substr($u[1], 6, 2)); + $uid = $u1 + ($u2 * 256); + $id = hex2bin(substr($u[1], 8, 18)); + $id = str_replace(chr(0), '', $id); + $state = hexdec(substr($u[1], 56, 2)); + $timestamp = Util::decodeTime(hexdec(Util::reverseHex(substr($u[1], 58, 8)))); + $type = hexdec(Util::reverseHex(substr($u[1], 66, 2 ))); + + $attendance[] = [ + 'uid' => $uid, + 'id' => $id, + 'state' => $state, + 'timestamp' => $timestamp, + 'type' => $type + ]; + + $attData = substr($attData, 40); + } + + } + + return $attendance; + } + + /** + * @param ZKTeco $self + * @return bool|mixed + */ + static public function clear(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_CLEAR_ATT_LOG; + $command_string = ''; + + return $self->_command($command, $command_string); + } +} diff --git a/vendor/rats/zkteco/src/Lib/Helper/Connect.php b/vendor/rats/zkteco/src/Lib/Helper/Connect.php new file mode 100644 index 0000000..9b59157 --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/Connect.php @@ -0,0 +1,83 @@ +_section = __METHOD__; + + $command = Util::CMD_CONNECT; + $command_string = ''; + $chksum = 0; + $session_id = 0; + $reply_id = -1 + Util::USHRT_MAX; + + $buf = Util::createHeader($command, $chksum, $session_id, $reply_id, $command_string); + + socket_sendto($self->_zkclient, $buf, strlen($buf), 0, $self->_ip, $self->_port); + + try { + @socket_recvfrom($self->_zkclient, $self->_data_recv, 1024, 0, $self->_ip, $self->_port); + if (strlen($self->_data_recv) > 0) { + $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6', substr($self->_data_recv, 0, 8)); + + $session = hexdec($u['h6'] . $u['h5']); + if (empty($session)) { + return false; + } + + $self->_session_id = $session; + return Util::checkValid($self->_data_recv); + } else { + return false; + } + } catch (ErrorException $e) { + return false; + } catch (Exception $e) { + return false; + } + } + + /** + * @param ZKTeco $self + * @return bool + */ + static public function disconnect(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_EXIT; + $command_string = ''; + $chksum = 0; + $session_id = $self->_session_id; + + $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6/H2h7/H2h8', substr($self->_data_recv, 0, 8)); + $reply_id = hexdec($u['h8'] . $u['h7']); + + $buf = Util::createHeader($command, $chksum, $session_id, $reply_id, $command_string); + + + socket_sendto($self->_zkclient, $buf, strlen($buf), 0, $self->_ip, $self->_port); + try { + @socket_recvfrom($self->_zkclient, $self->_data_recv, 1024, 0, $self->_ip, $self->_port); + + $self->_session_id = 0; + return Util::checkValid($self->_data_recv); + } catch (ErrorException $e) { + return false; + } catch (Exception $e) { + return false; + } + } +} \ No newline at end of file diff --git a/vendor/rats/zkteco/src/Lib/Helper/Device.php b/vendor/rats/zkteco/src/Lib/Helper/Device.php new file mode 100644 index 0000000..f5518ff --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/Device.php @@ -0,0 +1,151 @@ +_section = __METHOD__; + + $command = Util::CMD_DEVICE; + $command_string = '~DeviceName'; + + return $self->_command($command, $command_string); + } + + /** + * @param ZKTeco $self + * @return bool|mixed + */ + static public function enable(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_ENABLE_DEVICE; + $command_string = ''; + + return $self->_command($command, $command_string); + } + + /** + * @param ZKTeco $self + * @return bool|mixed + */ + static public function disable(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_DISABLE_DEVICE; + $command_string = chr(0) . chr(0); + + return $self->_command($command, $command_string); + } + + /** + * @param ZKTeco $self + * @return bool|mixed *** this will turn off the device + */ + public static function powerOff(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_POWEROFF; + $command_string = chr(0) . chr(0); + return $self->_command($command, $command_string); + } + + + /** + * @param ZKTeco $self + * @return bool|mixed *** this will restart the device + */ + public static function restart(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_RESTART; + $command_string = chr(0) . chr(0); + return $self->_command($command, $command_string); + } + + + /** + * @param ZKTeco $self + * @return bool|mixed *** this will sleep the device + */ + public static function sleep(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_SLEEP; + $command_string = chr(0) . chr(0); + return $self->_command($command, $command_string); + } + + + /** + * @param ZKTeco $self + * @return bool|mixed *** this will resume the device from sleep + */ + public static function resume(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_RESUME; + $command_string = chr(0) . chr(0); + return $self->_command($command, $command_string); + } + + + /** + * @param ZKTeco $self + * @return bool|mixed *** this will play voice "Thank you" + */ + public static function testVoice(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_TESTVOICE; + $command_string = chr(0) . chr(0); + return $self->_command($command, $command_string); + } + + + /** + * @param ZKTeco $self + * @return bool|mixed *** this will clear the LCD screen + */ + public static function clearLCD(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_CLEAR_LCD; + return $self->_command($command, ''); + } + + + /** + * @param ZKTeco $self + * @param $rank *** Line number of text + * @param $text *** Text which will display in the LCD screen + * @return bool|mixed *** this will write text into the LCD + */ + public static function writeLCD(ZKTeco $self, $rank, $text) + { + $self->_section = __METHOD__; + + $command = Util::CMD_WRITE_LCD; + $byte1 = chr((int)($rank % 256)); + $byte2 = chr((int)($rank >> 8)); + $byte3 = chr(0); + $command_string = $byte1.$byte2.$byte3.' '.$text; + return $self->_command($command, $command_string); + } +} \ No newline at end of file diff --git a/vendor/rats/zkteco/src/Lib/Helper/Face.php b/vendor/rats/zkteco/src/Lib/Helper/Face.php new file mode 100644 index 0000000..a0100a0 --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/Face.php @@ -0,0 +1,23 @@ +_section = __METHOD__; + + $command = Util::CMD_DEVICE; + $command_string = 'FaceFunOn'; + + return $self->_command($command, $command_string); + } +} + diff --git a/vendor/rats/zkteco/src/Lib/Helper/Fingerprint.php b/vendor/rats/zkteco/src/Lib/Helper/Fingerprint.php new file mode 100644 index 0000000..7891aac --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/Fingerprint.php @@ -0,0 +1,166 @@ +_section = __METHOD__; + + $data = []; + //fingers of the hands + for ($i = 0; $i <= 9; $i++) { + $finger = new Fingerprint(); + $tmp = $finger->_getFinger($self, $uid, $i); + if ($tmp['size'] > 0) { + $data[$i] = $tmp['tpl']; + } + unset($tmp); + } + return $data; + } + + + /** + * @param ZKTeco $self + * @param integer $uid Unique Employee ID in ZK device + * @param integer $finger Finger ID (0-9) + * @return array + */ + private function _getFinger(ZKTeco $self, $uid, $finger) + { + $command = Util::CMD_USER_TEMP_RRQ; + $byte1 = chr((int)($uid % 256)); + $byte2 = chr((int)($uid >> 8)); + $command_string = $byte1 . $byte2 . chr($finger); + + $ret = [ + 'size' => 0, + 'tpl' => '' + ]; + + $session = $self->_command($command, $command_string, Util::COMMAND_TYPE_DATA); + if ($session === false) { + return $ret; + } + + $data = Util::recData($self, 10, false); + + if (!empty($data)) { + $templateSize = strlen($data); + $prefix = chr($templateSize % 256) . chr(round($templateSize / 256)) . $byte1 . $byte2 . chr($finger) . chr(1); + $data = $prefix . $data; + if (strlen($templateSize) > 0) { + $ret['size'] = $templateSize; + $ret['tpl'] = $data; + } + } + + return $ret; + } + + /** + * TODO: Still can not set fingerprint. Need more documentation about it... + * + * @param ZKTeco $self + * @param int $uid Unique Employee ID in ZK device + * @param array $data Binary fingerprint data array (where key is finger ID (0-9) same like returned array from 'get' method) + * @return int Count of added fingerprints + */ + static public function set(ZKTeco $self, $uid, array $data) + { + $self->_section = __METHOD__; + + + $count = 0; + foreach ($data as $finger => $item) { + $allowSet = true; + $fingerPrint = new Fingerprint(); + if ($fingerPrint->_checkFinger($self, $uid, $finger) === true) { + $allowSet = $fingerPrint->_removeFinger($self, $uid, $finger); + } + if ($allowSet === true && $fingerPrint->_setFinger($self, $item) === true) { + $count++; + } + } + + return $count; + } + + /** + * @param ZKTeco $self + * @param string $data Binary fingerprint data item + * @return bool|mixed + */ + private function _setFinger(ZKTeco $self, $data) + { + $command = Util::CMD_USER_TEMP_WRQ; + $command_string = $data; + + return $self->_command($command, $command_string); + } + + /** + * @param ZKTeco $self + * @param int $uid Unique Employee ID in ZK device + * @param array $data Fingers ID array (0-9) + * @return int Count of deleted fingerprints + */ + static public function remove(ZKTeco $self, $uid, array $data) + { + $self->_section = __METHOD__; + + $count = 0; + foreach ($data as $finger) { + $fingerPrint = new Fingerprint(); + if ($fingerPrint->_checkFinger($self, $uid, $finger) === true) { + if ($fingerPrint->_removeFinger($self, $uid, $finger) === true) { + $count++; + } + } + } + + return $count; + } + + /** + * @param ZKTeco $self + * @param int $uid Unique Employee ID in ZK device + * @param int $finger Finger ID (0-9) + * @return bool + */ + private function _removeFinger(ZKTeco $self, $uid, $finger) + { + $command = Util::CMD_DELETE_USER_TEMP; + $byte1 = chr((int)($uid % 256)); + $byte2 = chr((int)($uid >> 8)); + $command_string = ($byte1 . $byte2) . chr($finger); + + $self->_command($command, $command_string); + $fingerPrint = new Fingerprint(); + return !($fingerPrint->_checkFinger($self, $uid, $finger)); + } + + /** + * @param ZKTeco $self + * @param int $uid Unique Employee ID in ZK device + * @param int $finger Finger ID (0-9) + * @return bool Returned true if exist + */ + private function _checkFinger(ZKTeco $self, $uid, $finger) + { + $fingerPrint = new Fingerprint(); + $res = $fingerPrint->_getFinger($self, $uid, $finger); + return (bool)($res['size'] > 0); + } +} \ No newline at end of file diff --git a/vendor/rats/zkteco/src/Lib/Helper/Os.php b/vendor/rats/zkteco/src/Lib/Helper/Os.php new file mode 100644 index 0000000..8776ea0 --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/Os.php @@ -0,0 +1,22 @@ +_section = __METHOD__; + + $command = Util::CMD_DEVICE; + $command_string = '~OS'; + + return $self->_command($command, $command_string); + } +} \ No newline at end of file diff --git a/vendor/rats/zkteco/src/Lib/Helper/Pin.php b/vendor/rats/zkteco/src/Lib/Helper/Pin.php new file mode 100644 index 0000000..cf35deb --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/Pin.php @@ -0,0 +1,22 @@ +_section = __METHOD__; + + $command = Util::CMD_DEVICE; + $command_string = '~PIN2Width'; + + return $self->_command($command, $command_string); + } +} \ No newline at end of file diff --git a/vendor/rats/zkteco/src/Lib/Helper/Platform.php b/vendor/rats/zkteco/src/Lib/Helper/Platform.php new file mode 100644 index 0000000..26c5209 --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/Platform.php @@ -0,0 +1,36 @@ +_section = __METHOD__; + + $command = Util::CMD_DEVICE; + $command_string = '~Platform'; + + return $self->_command($command, $command_string); + } + + /** + * @param ZKTeco $self + * @return bool|mixed + */ + static public function getVersion(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_DEVICE; + $command_string = '~ZKFPVersion'; + + return $self->_command($command, $command_string); + } +} \ No newline at end of file diff --git a/vendor/rats/zkteco/src/Lib/Helper/SerialNumber.php b/vendor/rats/zkteco/src/Lib/Helper/SerialNumber.php new file mode 100644 index 0000000..571406f --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/SerialNumber.php @@ -0,0 +1,22 @@ +_section = __METHOD__; + + $command = Util::CMD_DEVICE; + $command_string = '~SerialNumber'; + + return $self->_command($command, $command_string); + } +} \ No newline at end of file diff --git a/vendor/rats/zkteco/src/Lib/Helper/Ssr.php b/vendor/rats/zkteco/src/Lib/Helper/Ssr.php new file mode 100644 index 0000000..13075aa --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/Ssr.php @@ -0,0 +1,22 @@ +_section = __METHOD__; + + $command = Util::CMD_DEVICE; + $command_string = '~SSR'; + + return $self->_command($command, $command_string); + } +} \ No newline at end of file diff --git a/vendor/rats/zkteco/src/Lib/Helper/Time.php b/vendor/rats/zkteco/src/Lib/Helper/Time.php new file mode 100644 index 0000000..35d3472 --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/Time.php @@ -0,0 +1,44 @@ +_section = __METHOD__; + + $command = Util::CMD_SET_TIME; + $command_string = pack('I', Util::encodeTime($t)); + + return $self->_command($command, $command_string); + } + + /** + * @param ZKTeco $self + * @return bool|mixed + */ + static public function get(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_GET_TIME; + $command_string = ''; + + $ret = $self->_command($command, $command_string); + + if ($ret) { + return Util::decodeTime(hexdec(Util::reverseHex(bin2hex($ret)))); + } else { + return false; + } + } +} \ No newline at end of file diff --git a/vendor/rats/zkteco/src/Lib/Helper/User.php b/vendor/rats/zkteco/src/Lib/Helper/User.php new file mode 100644 index 0000000..428ca99 --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/User.php @@ -0,0 +1,161 @@ +_section = __METHOD__; + + if ( + (int)$uid === 0 || + (int)$uid > Util::USHRT_MAX || + strlen($userid) > 9 || + strlen($name) > 24 || + strlen($password) > 8 || + strlen($cardno) > 10 + ) { + return false; + } + + $command = Util::CMD_SET_USER; + $byte1 = chr((int)($uid % 256)); + $byte2 = chr((int)($uid >> 8)); + $cardno = hex2bin(Util::reverseHex(dechex($cardno))); + + $command_string = implode('', [ + $byte1, + $byte2, + chr($role), + str_pad($password, 8, chr(0)), + str_pad($name, 24, chr(0)), + str_pad($cardno, 4, chr(0)), + str_pad(chr(1), 9, chr(0)), + str_pad($userid, 9, chr(0)), + str_repeat(chr(0), 15) + ]); +// die($command_string); + return $self->_command($command, $command_string); + } + + /** + * @param ZKTeco $self + * @return array [userid, name, cardno, uid, role, password] + */ + static public function get(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_USER_TEMP_RRQ; + $command_string = chr(Util::FCT_USER); + + $session = $self->_command($command, $command_string, Util::COMMAND_TYPE_DATA); + if ($session === false) { + return []; + } + + $userData = Util::recData($self); + + $users = []; + if (!empty($userData)) { + $userData = substr($userData, 11); + + while (strlen($userData) > 72) { + $u = unpack('H144', substr($userData, 0, 72)); + + $u1 = hexdec(substr($u[1], 2, 2)); + $u2 = hexdec(substr($u[1], 4, 2)); + $uid = $u1 + ($u2 * 256); + $cardno = hexdec(substr($u[1], 78, 2) . substr($u[1], 76, 2) . substr($u[1], 74, 2) . substr($u[1], 72, 2)) . ' '; + $role = hexdec(substr($u[1], 6, 2)) . ' '; + $password = hex2bin(substr($u[1], 8, 16)) . ' '; + $name = hex2bin(substr($u[1], 24, 74)) . ' '; + $userid = hex2bin(substr($u[1], 98, 72)) . ' '; + + //Clean up some messy characters from the user name + $password = explode(chr(0), $password, 2); + $password = $password[0]; + $userid = explode(chr(0), $userid, 2); + $userid = $userid[0]; + $name = explode(chr(0), $name, 3); + $name = utf8_encode($name[0]); + $cardno = str_pad($cardno, 11, '0', STR_PAD_LEFT); + + if ($name == '') { + $name = $userid; + } + + $users[$userid] = [ + 'uid' => $uid, + 'userid' => $userid, + 'name' => $name, + 'role' => intval($role), + 'password' => $password, + 'cardno' => $cardno, + ]; + + $userData = substr($userData, 72); + } + } + + return $users; + } + + /** + * @param ZKTeco $self + * @return bool|mixed + */ + static public function clear(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_CLEAR_DATA; + $command_string = ''; + + return $self->_command($command, $command_string); + } + + /** + * @param ZKTeco $self + * @return bool|mixed + */ + static public function clearAdmin(ZKTeco $self) + { + $self->_section = __METHOD__; + + $command = Util::CMD_CLEAR_ADMIN; + $command_string = ''; + + return $self->_command($command, $command_string); + } + + /** + * @param ZKTeco $self + * @param integer $uid + * @return bool|mixed + */ + static public function remove(ZKTeco $self, $uid) + { + $self->_section = __METHOD__; + + $command = Util::CMD_DELETE_USER; + $byte1 = chr((int)($uid % 256)); + $byte2 = chr((int)($uid >> 8)); + $command_string = ($byte1 . $byte2); + + return $self->_command($command, $command_string); + } +} \ No newline at end of file diff --git a/vendor/rats/zkteco/src/Lib/Helper/Util.php b/vendor/rats/zkteco/src/Lib/Helper/Util.php new file mode 100644 index 0000000..b0a3f41 --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/Util.php @@ -0,0 +1,427 @@ + (int)date('Y', $timestamp), + 'month' => (int)date('m', $timestamp), + 'day' => (int)date('d', $timestamp), + 'hour' => (int)date('H', $timestamp), + 'minute' => (int)date('i', $timestamp), + 'second' => (int)date('s', $timestamp), + ]; + + $d = (($t->year % 100) * 12 * 31 + (($t->month - 1) * 31) + $t->day - 1) * + (24 * 60 * 60) + ($t->hour * 60 + $t->minute) * 60 + $t->second; + + return $d; + } + + /** + * Decode a timestamp retrieved from the timeclock + * copied from zkemsdk.c - DecodeTime + * + * @param int|string $t + * @return false|string Format: "Y-m-d H:i:s" + */ + static public function decodeTime($t) + { + $second = $t % 60; + $t = $t / 60; + + $minute = $t % 60; + $t = $t / 60; + + $hour = $t % 24; + $t = $t / 24; + + $day = $t % 31 + 1; + $t = $t / 31; + + $month = $t % 12 + 1; + $t = $t / 12; + + $year = floor($t + 2000); + + $d = date('Y-m-d H:i:s', strtotime( + $year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second + )); + + return $d; + } + + /** + * @param string $hex + * @return string + */ + static public function reverseHex($hex) + { + $tmp = ''; + + for ($i = strlen($hex); $i >= 0; $i--) { + $tmp .= substr($hex, $i, 2); + $i--; + } + + return $tmp; + } + + /** + * Checks a returned packet to see if it returned self::CMD_PREPARE_DATA, + * indicating that data packets are to be sent + * Returns the amount of bytes that are going to be sent + * + * @param ZKTeco $self + * @return bool|number + */ + static public function getSize(ZKTeco $self) + { + $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6/H2h7/H2h8', substr($self->_data_recv, 0, 8)); + $command = hexdec($u['h2'] . $u['h1']); + + if ($command == self::CMD_PREPARE_DATA) { + $u = unpack('H2h1/H2h2/H2h3/H2h4', substr($self->_data_recv, 8, 4)); + $size = hexdec($u['h4'] . $u['h3'] . $u['h2'] . $u['h1']); + return $size; + } else { + return false; + } + } + + /** + * This function calculates the chksum of the packet to be sent to the + * time clock + * Copied from zkemsdk.c + * + * @inheritdoc + */ + static public function createChkSum($p) + { + $l = count($p); + $chksum = 0; + $i = $l; + $j = 1; + while ($i > 1) { + $u = unpack('S', pack('C2', $p['c' . $j], $p['c' . ($j + 1)])); + + $chksum += $u[1]; + + if ($chksum > self::USHRT_MAX) { + $chksum -= self::USHRT_MAX; + } + $i -= 2; + $j += 2; + } + + if ($i) { + $chksum = $chksum + $p['c' . strval(count($p))]; + } + + while ($chksum > self::USHRT_MAX) { + $chksum -= self::USHRT_MAX; + } + + if ($chksum > 0) { + $chksum = -($chksum); + } else { + $chksum = abs($chksum); + } + + $chksum -= 1; + while ($chksum < 0) { + $chksum += self::USHRT_MAX; + } + + return pack('S', $chksum); + } + + /** + * This function puts a the parts that make up a packet together and + * packs them into a byte string + * + * @inheritdoc + */ + static public function createHeader($command, $chksum, $session_id, $reply_id, $command_string) + { + $buf = pack('SSSS', $command, $chksum, $session_id, $reply_id) . $command_string; + + $buf = unpack('C' . (8 + strlen($command_string)) . 'c', $buf); + + $u = unpack('S', self::createChkSum($buf)); + + if (is_array($u)) { + $u = reset($u); + } + $chksum = $u; + + $reply_id += 1; + + if ($reply_id >= self::USHRT_MAX) { + $reply_id -= self::USHRT_MAX; + } + + $buf = pack('SSSS', $command, $chksum, $session_id, $reply_id); + + return $buf . $command_string; + + } + + /** + * Checks a returned packet to see if it returned Util::CMD_ACK_OK, + * indicating success + * + * @inheritdoc + */ + static public function checkValid($reply) + { + $u = unpack('H2h1/H2h2', substr($reply, 0, 8)); + + $command = hexdec($u['h2'] . $u['h1']); + /** TODO: Some device can return 'Connection unauthorized' then should check also */ + if ($command == self::CMD_ACK_OK || $command == self::CMD_ACK_UNAUTH) { + return true; + } else { + return false; + } + } + + /** + * Get User Role string + * @param integer $role + * @return string + */ + static public function getUserRole($role) + { + switch ($role) { + case self::LEVEL_USER: + $ret = 'User'; + break; + case self::LEVEL_ADMIN: + $ret = 'Admin'; + break; + default: + $ret = 'Unknown'; + } + + return $ret; + } + + /** + * Get Attendance State string + * @param integer $state + * @return string + */ + static public function getAttState($state) + { + switch ($state) { + case self::ATT_STATE_FINGERPRINT: + $ret = 'Fingerprint'; + break; + case self::ATT_STATE_PASSWORD: + $ret = 'Password'; + break; + case self::ATT_STATE_CARD: + $ret = 'Card'; + break; + default: + $ret = 'Unknown'; + } + + return $ret; + } + + /** + * Get Attendance Type string + * @param integer $type + * @return string + */ + static public function getAttType($type) + { + switch ($type) { + case self::ATT_TYPE_CHECK_IN: + $ret = 'Check-in'; + break; + case self::ATT_TYPE_CHECK_OUT: + $ret = 'Check-out'; + break; + case self::ATT_TYPE_OVERTIME_IN: + $ret = 'Overtime-in'; + break; + case self::ATT_TYPE_OVERTIME_OUT: + $ret = 'Overtime-out'; + break; + default: + $ret = 'Undefined'; + } + + return $ret; + } + + /** + * Receive data from device + * @param ZKTeco $self + * @param int $maxErrors + * @param bool $first if 'true' don't remove first 4 bytes for first row + * @return string + */ + static public function recData(ZKTeco $self, $maxErrors = 10, $first = true) + { + $data = ''; + $bytes = self::getSize($self); + + if ($bytes) { + $received = 0; + $errors = 0; + + while ($bytes > $received) { + $ret = @socket_recvfrom($self->_zkclient, $dataRec, 1032, 0, $self->_ip, $self->_port); + + if ($ret === false) { + if ($errors < $maxErrors) { + //try again if false + $errors++; + sleep(1); + continue; + } else { + //return empty if has maximum count of errors + self::logReceived($self, $received, $bytes); + unset($data); + return ''; + } + } + + if ($first === false) { + //The first 4 bytes don't seem to be related to the user + $dataRec = substr($dataRec, 8); + } + + $data .= $dataRec; + $received += strlen($dataRec); + + unset($dataRec); + $first = false; + } + + //flush socket + @socket_recvfrom($self->_zkclient, $dataRec, 1024, 0, $self->_ip, $self->_port); + unset($dataRec); + } + + return $data; + } + + /** + * @param ZKTeco $self + * @param int $received + * @param int $bytes + */ + static private function logReceived(ZKTeco $self, $received, $bytes) + { + self::logger($self, 'Received: ' . $received . ' of ' . $bytes . ' bytes'); + } + + /** + * Write log + * @param ZKTeco $self + * @param string $str + */ + static private function logger(ZKTeco $self, $str) + { + if (defined('ZK_LIB_LOG')) { + //use constant if defined + $log = ZK_LIB_LOG; + } else { + $dir = dirname(dirname(__FILE__)); + $log = $dir . '/logs/error.log'; + } + + $row = '<' . $self->_ip . '> [' . date('d.m.Y H:i:s') . '] '; + $row .= (empty($self->_section) ? '' : '(' . $self->_section . ') '); + $row .= $str; + $row .= PHP_EOL; + + file_put_contents($log, $row, FILE_APPEND); + } +} diff --git a/vendor/rats/zkteco/src/Lib/Helper/Version.php b/vendor/rats/zkteco/src/Lib/Helper/Version.php new file mode 100644 index 0000000..6ff0810 --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/Version.php @@ -0,0 +1,23 @@ +_section = __METHOD__; + + $command = Util::CMD_VERSION; + $command_string = ''; + + return $self->_command($command, $command_string); + } +} \ No newline at end of file diff --git a/vendor/rats/zkteco/src/Lib/Helper/WorkCode.php b/vendor/rats/zkteco/src/Lib/Helper/WorkCode.php new file mode 100644 index 0000000..9be4011 --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/Helper/WorkCode.php @@ -0,0 +1,22 @@ +_section = __METHOD__; + + $command = Util::CMD_DEVICE; + $command_string = 'WorkCode'; + + return $self->_command($command, $command_string); + } +} diff --git a/vendor/rats/zkteco/src/Lib/ZKTeco.php b/vendor/rats/zkteco/src/Lib/ZKTeco.php new file mode 100644 index 0000000..f433078 --- /dev/null +++ b/vendor/rats/zkteco/src/Lib/ZKTeco.php @@ -0,0 +1,435 @@ +_ip = $ip; + $this->_port = $port; + + $this->_zkclient = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + + $timeout = array('sec' => 60, 'usec' => 500000); + socket_set_option($this->_zkclient, SOL_SOCKET, SO_RCVTIMEO, $timeout); + + } + + /** + * Create and send command to device + * + * @param string $command + * @param string $command_string + * @param string $type + * @return bool|mixed + */ + public function _command($command, $command_string, $type = Util::COMMAND_TYPE_GENERAL) + { + $chksum = 0; + $session_id = $this->_session_id; + + $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6/H2h7/H2h8', substr($this->_data_recv, 0, 8)); + $reply_id = hexdec($u['h8'] . $u['h7']); + + $buf = Util::createHeader($command, $chksum, $session_id, $reply_id, $command_string); + + socket_sendto($this->_zkclient, $buf, strlen($buf), 0, $this->_ip, $this->_port); + + try { + @socket_recvfrom($this->_zkclient, $this->_data_recv, 1024, 0, $this->_ip, $this->_port); + + $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6', substr($this->_data_recv, 0, 8)); + + $ret = false; + $session = hexdec($u['h6'] . $u['h5']); + + if ($type === Util::COMMAND_TYPE_GENERAL && $session_id === $session) { + $ret = substr($this->_data_recv, 8); + } else if ($type === Util::COMMAND_TYPE_DATA && !empty($session)) { + $ret = $session; + } + + return $ret; + } catch (ErrorException $e) { + return false; + } catch (Exception $e) { + return false; + } + } + + /** + * Connect to device + * + * @return bool + */ + public function connect() + { + return Connect::connect($this); + } + + /** + * Disconnect from device + * + * @return bool + */ + public function disconnect() + { + return Connect::disconnect($this); + } + + /** + * Get device version + * + * @return bool|mixed + */ + public function version() + { + return Version::get($this); + } + + /** + * Get OS version + * + * @return bool|mixed + */ + public function osVersion() + { + return Os::get($this); + } + + /** + * Get platform + * + * @return bool|mixed + */ + public function platform() + { + return Platform::get($this); + } + + /** + * Get firmware version + * + * @return bool|mixed + */ + public function fmVersion() + { + return Platform::getVersion($this); + } + + /** + * Get work code + * + * @return bool|mixed + */ + public function workCode() + { + return WorkCode::get($this); + } + + /** + * Get SSR + * + * @return bool|mixed + */ + public function ssr() + { + return Ssr::get($this); + } + + /** + * Get pin width + * + * @return bool|mixed + */ + public function pinWidth() + { + return Pin::width($this); + } + + /** + * @return bool|mixed + */ + public function faceFunctionOn() + { + return Face::on($this); + } + + /** + * Get device serial number + * + * @return bool|mixed + */ + public function serialNumber() + { + return SerialNumber::get($this); + } + + /** + * Get device name + * + * @return bool|mixed + */ + public function deviceName() + { + return Device::name($this); + } + + /** + * Disable device + * + * @return bool|mixed + */ + public function disableDevice() + { + return Device::disable($this); + } + + /** + * Enable device + * + * @return bool|mixed + */ + public function enableDevice() + { + return Device::enable($this); + } + + /** + * Get users data + * + * @return array [userid, name, cardno, uid, role, password] + */ + public function getUser() + { + return User::get($this); + } + + /** + * Set user data + * + * @param int $uid Unique ID (max 65535) + * @param int|string $userid ID in DB (same like $uid, max length = 9, only numbers - depends device setting) + * @param string $name (max length = 24) + * @param int|string $password (max length = 8, only numbers - depends device setting) + * @param int $role Default Util::LEVEL_USER + * @param int $cardno Default 0 (max length = 10, only numbers) + * @return bool|mixed + */ + public function setUser($uid, $userid, $name, $password, $role = Util::LEVEL_USER, $cardno = 0) + { + return User::set($this, $uid, $userid, $name, $password, $role, $cardno); + } + + + + /** + * Remove All users + * + * @return bool|mixed + */ + public function clearUsers() + { + return User::clear($this); + } + + /** + * Remove admin + * + * @return bool|mixed + */ + public function clearAdmin() + { + return User::clearAdmin($this); + } + + /** + * Remove user by UID + * + * @param integer $uid + * @return bool|mixed + */ + public function removeUser($uid) + { + return User::remove($this, $uid); + } + + + + /** + * Get fingerprint data array by UID + * TODO: Can get data, but don't know how to parse the data. Need more documentation about it... + * + * @param integer $uid Unique ID (max 65535) + * @return array Binary fingerprint data array (where key is finger ID (0-9)) + */ + public function getFingerprint($uid) + { + return Fingerprint::get($this, $uid); + } + + /** + * Set fingerprint data array + * TODO: Still can not set fingerprint. Need more documentation about it... + * + * @param integer $uid Unique ID (max 65535) + * @param array $data Binary fingerprint data array (where key is finger ID (0-9) same like returned array from 'getFingerprint' method) + * @return int Count of added fingerprints + */ + public function setFingerprint($uid, array $data) + { + return Fingerprint::set($this, $uid, $data); + } + + /** + * Remove fingerprint by UID and fingers ID array + * + * @param integer $uid Unique ID (max 65535) + * @param array $data Fingers ID array (0-9) + * @return int Count of deleted fingerprints + */ + public function removeFingerprint($uid, array $data) + { + return Fingerprint::remove($this, $uid, $data); + } + + + /** + * Get attendance log + * + * @return array [uid, id, state, timestamp] + */ + public function getAttendance() + { + return Attendance::get($this); + } + + /** + * Clear attendance log + * + * @return bool|mixed + */ + public function clearAttendance() + { + return Attendance::clear($this); + } + + /** + * Set device time + * + * @param string $t Format: "Y-m-d H:i:s" + * @return bool|mixed + */ + public function setTime($t) + { + return Time::set($this, $t); + } + + /** + * Get device time + * + * @return bool|mixed Format: "Y-m-d H:i:s" + */ + public function getTime() + { + return Time::get($this); + } + + + /** + * turn off the device + * + * @return bool|mixed + */ + public function shutdown() + { + return Device::powerOff($this); + } + + /** + * restart the device + * + * @return bool|mixed + */ + public function restart() + { + return Device::restart($this); + } + + + /** + * make sleep mood the device + * + * @return bool|mixed + */ + public function sleep() + { + return Device::sleep($this); + } + + + /** + * resume the device from sleep + * + * @return bool|mixed + */ + public function resume() + { + return Device::resume($this); + } + + + /** + * voice test Sound will "Thank you" + * + * @return bool|mixed + */ + public function testVoice() + { + return Device::testVoice($this); + } + + + public function clearLCD() + { + return Device::clearLCD($this); + } + + + public function writeLCD() + { + return Device::writeLCD($this, 2, "RAIHAN Afroz Topu"); + } +} \ No newline at end of file diff --git a/vendor/rats/zkteco/src/Providers/ZktecoServiceProvider.php b/vendor/rats/zkteco/src/Providers/ZktecoServiceProvider.php new file mode 100644 index 0000000..2820161 --- /dev/null +++ b/vendor/rats/zkteco/src/Providers/ZktecoServiceProvider.php @@ -0,0 +1,16 @@ +