├── zklib ├── logs │ └── .gitignore ├── docs │ └── ZK_Communication_protocol_manual_CMD.pdf ├── vendor │ ├── autoload.php │ └── composer │ │ ├── autoload_classmap.php │ │ ├── autoload_namespaces.php │ │ ├── autoload_psr4.php │ │ ├── autoload_real.php │ │ └── ClassLoader.php ├── src │ ├── Os.php │ ├── Ssr.php │ ├── Version.php │ ├── Pin.php │ ├── Face.php │ ├── WorkCode.php │ ├── SerialNumber.php │ ├── Platform.php │ ├── Time.php │ ├── Device.php │ ├── Attendance.php │ ├── Connect.php │ ├── Fingerprint.php │ ├── User.php │ └── Util.php └── ZKLib.php ├── README.md ├── example.php └── LICENSE /zklib/logs/.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !/.gitignore 3 | -------------------------------------------------------------------------------- /zklib/docs/ZK_Communication_protocol_manual_CMD.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vodvud/php_zklib/HEAD/zklib/docs/ZK_Communication_protocol_manual_CMD.pdf -------------------------------------------------------------------------------- /zklib/vendor/autoload.php: -------------------------------------------------------------------------------- 1 | array($baseDir . '/src'), 10 | ); 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### *** Note: Abandoned library *** 2 | 3 | # PHP ZKLib # 4 | 5 | PHP library to interacts with ZK Time and Attendance Devices. 6 | 7 | Library for connecting under the network using the UDP protocol and port 4370 8 | 9 | See ZK communication protocol manual [here](zklib/docs/ZK_Communication_protocol_manual_CMD.pdf) 10 | -------------------------------------------------------------------------------- /zklib/src/Os.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 16 | 17 | $command = Util::CMD_DEVICE; 18 | $command_string = '~OS'; 19 | 20 | return $self->_command($command, $command_string); 21 | } 22 | } -------------------------------------------------------------------------------- /zklib/src/Ssr.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 16 | 17 | $command = Util::CMD_DEVICE; 18 | $command_string = '~SSR'; 19 | 20 | return $self->_command($command, $command_string); 21 | } 22 | } -------------------------------------------------------------------------------- /zklib/src/Version.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 16 | 17 | $command = Util::CMD_VERSION; 18 | $command_string = ''; 19 | 20 | return $self->_command($command, $command_string); 21 | } 22 | } -------------------------------------------------------------------------------- /zklib/src/Pin.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 16 | 17 | $command = Util::CMD_DEVICE; 18 | $command_string = '~PIN2Width'; 19 | 20 | return $self->_command($command, $command_string); 21 | } 22 | } -------------------------------------------------------------------------------- /zklib/src/Face.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 16 | 17 | $command = Util::CMD_DEVICE; 18 | $command_string = 'FaceFunOn'; 19 | 20 | return $self->_command($command, $command_string); 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /zklib/src/WorkCode.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 16 | 17 | $command = Util::CMD_DEVICE; 18 | $command_string = 'WorkCode'; 19 | 20 | return $self->_command($command, $command_string); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /zklib/src/SerialNumber.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 16 | 17 | $command = Util::CMD_DEVICE; 18 | $command_string = '~SerialNumber'; 19 | 20 | return $self->_command($command, $command_string); 21 | } 22 | } -------------------------------------------------------------------------------- /zklib/src/Platform.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 16 | 17 | $command = Util::CMD_DEVICE; 18 | $command_string = '~Platform'; 19 | 20 | return $self->_command($command, $command_string); 21 | } 22 | 23 | /** 24 | * @param ZKLib $self 25 | * @return bool|mixed 26 | */ 27 | public function getVersion(ZKLib $self) 28 | { 29 | $self->_section = __METHOD__; 30 | 31 | $command = Util::CMD_DEVICE; 32 | $command_string = '~ZKFPVersion'; 33 | 34 | return $self->_command($command, $command_string); 35 | } 36 | } -------------------------------------------------------------------------------- /zklib/src/Time.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 17 | 18 | $command = Util::CMD_SET_TIME; 19 | $command_string = pack('I', Util::encodeTime($t)); 20 | 21 | return $self->_command($command, $command_string); 22 | } 23 | 24 | /** 25 | * @param ZKLib $self 26 | * @return bool|mixed 27 | */ 28 | public function get(ZKLib $self) 29 | { 30 | $self->_section = __METHOD__; 31 | 32 | $command = Util::CMD_GET_TIME; 33 | $command_string = ''; 34 | 35 | $ret = $self->_command($command, $command_string); 36 | 37 | if ($ret) { 38 | return Util::decodeTime(hexdec(Util::reverseHex(bin2hex($ret)))); 39 | } else { 40 | return false; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /zklib/src/Device.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 16 | 17 | $command = Util::CMD_DEVICE; 18 | $command_string = '~DeviceName'; 19 | 20 | return $self->_command($command, $command_string); 21 | } 22 | 23 | /** 24 | * @param ZKLib $self 25 | * @return bool|mixed 26 | */ 27 | public function enable(ZKLib $self) 28 | { 29 | $self->_section = __METHOD__; 30 | 31 | $command = Util::CMD_ENABLE_DEVICE; 32 | $command_string = ''; 33 | 34 | return $self->_command($command, $command_string); 35 | } 36 | 37 | /** 38 | * @param ZKLib $self 39 | * @return bool|mixed 40 | */ 41 | public function disable(ZKLib $self) 42 | { 43 | $self->_section = __METHOD__; 44 | 45 | $command = Util::CMD_DISABLE_DEVICE; 46 | $command_string = chr(0) . chr(0); 47 | 48 | return $self->_command($command, $command_string); 49 | } 50 | } -------------------------------------------------------------------------------- /zklib/vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | $path) { 28 | $loader->set($namespace, $path); 29 | } 30 | 31 | $map = require __DIR__ . '/autoload_psr4.php'; 32 | foreach ($map as $namespace => $path) { 33 | $loader->setPsr4($namespace, $path); 34 | } 35 | 36 | $classMap = require __DIR__ . '/autoload_classmap.php'; 37 | if ($classMap) { 38 | $loader->addClassMap($classMap); 39 | } 40 | 41 | $loader->register(true); 42 | 43 | return $loader; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /zklib/src/Attendance.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 16 | 17 | $command = Util::CMD_ATT_LOG_RRQ; 18 | $command_string = ''; 19 | 20 | $session = $self->_command($command, $command_string, Util::COMMAND_TYPE_DATA); 21 | if ($session === false) { 22 | return []; 23 | } 24 | 25 | $attData = Util::recData($self); 26 | 27 | $attendance = []; 28 | if (!empty($attData)) { 29 | $attData = substr($attData, 10); 30 | 31 | while (strlen($attData) > 40) { 32 | $u = unpack('H78', substr($attData, 0, 39)); 33 | 34 | $u1 = hexdec(substr($u[1], 4, 2)); 35 | $u2 = hexdec(substr($u[1], 6, 2)); 36 | $uid = $u1 + ($u2 * 256); 37 | $id = hex2bin(substr($u[1], 8, 18)); 38 | $id = str_replace(chr(0), '', $id); 39 | $state = hexdec(substr($u[1], 56, 2)); 40 | $timestamp = Util::decodeTime(hexdec(Util::reverseHex(substr($u[1], 58, 8)))); 41 | $type = hexdec(Util::reverseHex(substr($u[1], 66, 2 ))); 42 | 43 | $attendance[] = [ 44 | 'uid' => $uid, 45 | 'id' => $id, 46 | 'state' => $state, 47 | 'timestamp' => $timestamp, 48 | 'type' => $type 49 | ]; 50 | 51 | $attData = substr($attData, 40); 52 | } 53 | 54 | } 55 | 56 | return $attendance; 57 | } 58 | 59 | /** 60 | * @param ZKLib $self 61 | * @return bool|mixed 62 | */ 63 | public function clear(ZKLib $self) 64 | { 65 | $self->_section = __METHOD__; 66 | 67 | $command = Util::CMD_CLEAR_ATT_LOG; 68 | $command_string = ''; 69 | 70 | return $self->_command($command, $command_string); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /zklib/src/Connect.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 18 | 19 | $command = Util::CMD_CONNECT; 20 | $command_string = ''; 21 | $chksum = 0; 22 | $session_id = 0; 23 | $reply_id = -1 + Util::USHRT_MAX; 24 | 25 | $buf = Util::createHeader($command, $chksum, $session_id, $reply_id, $command_string); 26 | 27 | socket_sendto($self->_zkclient, $buf, strlen($buf), 0, $self->_ip, $self->_port); 28 | 29 | try { 30 | @socket_recvfrom($self->_zkclient, $self->_data_recv, 1024, 0, $self->_ip, $self->_port); 31 | if (strlen($self->_data_recv) > 0) { 32 | $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6', substr($self->_data_recv, 0, 8)); 33 | 34 | $session = hexdec($u['h6'] . $u['h5']); 35 | if (empty($session)) { 36 | return false; 37 | } 38 | 39 | $self->_session_id = $session; 40 | return Util::checkValid($self->_data_recv); 41 | } else { 42 | return false; 43 | } 44 | } catch (ErrorException $e) { 45 | return false; 46 | } catch (Exception $e) { 47 | return false; 48 | } 49 | } 50 | 51 | /** 52 | * @param ZKLib $self 53 | * @return bool 54 | */ 55 | public function disconnect(ZKLib $self) 56 | { 57 | $self->_section = __METHOD__; 58 | 59 | $command = Util::CMD_EXIT; 60 | $command_string = ''; 61 | $chksum = 0; 62 | $session_id = $self->_session_id; 63 | 64 | $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6/H2h7/H2h8', substr($self->_data_recv, 0, 8)); 65 | $reply_id = hexdec($u['h8'] . $u['h7']); 66 | 67 | $buf = Util::createHeader($command, $chksum, $session_id, $reply_id, $command_string); 68 | 69 | 70 | socket_sendto($self->_zkclient, $buf, strlen($buf), 0, $self->_ip, $self->_port); 71 | try { 72 | @socket_recvfrom($self->_zkclient, $self->_data_recv, 1024, 0, $self->_ip, $self->_port); 73 | 74 | $self->_session_id = 0; 75 | return Util::checkValid($self->_data_recv); 76 | } catch (ErrorException $e) { 77 | return false; 78 | } catch (Exception $e) { 79 | return false; 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /zklib/src/Fingerprint.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 19 | 20 | $data = []; 21 | //fingers of the hands 22 | for ($i = 0; $i <= 9; $i++) { 23 | $tmp = $this->_getFinger($self, $uid, $i); 24 | if ($tmp['size'] > 0) { 25 | $data[$i] = $tmp['tpl']; 26 | } 27 | unset($tmp); 28 | } 29 | return $data; 30 | } 31 | 32 | 33 | /** 34 | * @param ZKLib $self 35 | * @param integer $uid Unique Employee ID in ZK device 36 | * @param integer $finger Finger ID (0-9) 37 | * @return array 38 | */ 39 | private function _getFinger(ZKLib $self, $uid, $finger) 40 | { 41 | $command = Util::CMD_USER_TEMP_RRQ; 42 | $byte1 = chr((int)($uid % 256)); 43 | $byte2 = chr((int)($uid >> 8)); 44 | $command_string = $byte1 . $byte2 . chr($finger); 45 | 46 | $ret = [ 47 | 'size' => 0, 48 | 'tpl' => '' 49 | ]; 50 | 51 | $session = $self->_command($command, $command_string, Util::COMMAND_TYPE_DATA); 52 | if ($session === false) { 53 | return $ret; 54 | } 55 | 56 | $data = Util::recData($self, 10, false); 57 | 58 | if (!empty($data)) { 59 | $templateSize = strlen($data); 60 | $prefix = chr($templateSize % 256) . chr(round($templateSize / 256)) . $byte1 . $byte2 . chr($finger) . chr(1); 61 | $data = $prefix . $data; 62 | if (strlen($templateSize) > 0) { 63 | $ret['size'] = $templateSize; 64 | $ret['tpl'] = $data; 65 | } 66 | } 67 | 68 | return $ret; 69 | } 70 | 71 | /** 72 | * TODO: Still can not set fingerprint. Need more documentation about it... 73 | * 74 | * @param ZKLib $self 75 | * @param int $uid Unique Employee ID in ZK device 76 | * @param array $data Binary fingerprint data array (where key is finger ID (0-9) same like returned array from 'get' method) 77 | * @return int Count of added fingerprints 78 | */ 79 | public function set(ZKLib $self, $uid, array $data) 80 | { 81 | $self->_section = __METHOD__; 82 | 83 | 84 | $count = 0; 85 | foreach ($data as $finger => $item) { 86 | $allowSet = true; 87 | if ($this->_checkFinger($self, $uid, $finger) === true) { 88 | $allowSet = $this->_removeFinger($self, $uid, $finger); 89 | } 90 | if ($allowSet === true && $this->_setFinger($self, $item) === true) { 91 | $count++; 92 | } 93 | } 94 | 95 | return $count; 96 | } 97 | 98 | /** 99 | * @param ZKLib $self 100 | * @param string $data Binary fingerprint data item 101 | * @return bool|mixed 102 | */ 103 | private function _setFinger(ZKLib $self, $data) 104 | { 105 | $command = Util::CMD_USER_TEMP_WRQ; 106 | $command_string = $data; 107 | 108 | return $self->_command($command, $command_string); 109 | } 110 | 111 | /** 112 | * @param ZKLib $self 113 | * @param int $uid Unique Employee ID in ZK device 114 | * @param array $data Fingers ID array (0-9) 115 | * @return int Count of deleted fingerprints 116 | */ 117 | public function remove(ZKLib $self, $uid, array $data) 118 | { 119 | $self->_section = __METHOD__; 120 | 121 | $count = 0; 122 | foreach ($data as $finger) { 123 | if ($this->_checkFinger($self, $uid, $finger) === true) { 124 | if ($this->_removeFinger($self, $uid, $finger) === true) { 125 | $count++; 126 | } 127 | } 128 | } 129 | 130 | return $count; 131 | } 132 | 133 | /** 134 | * @param ZKLib $self 135 | * @param int $uid Unique Employee ID in ZK device 136 | * @param int $finger Finger ID (0-9) 137 | * @return bool 138 | */ 139 | private function _removeFinger(ZKLib $self, $uid, $finger) 140 | { 141 | $command = Util::CMD_DELETE_USER_TEMP; 142 | $byte1 = chr((int)($uid % 256)); 143 | $byte2 = chr((int)($uid >> 8)); 144 | $command_string = ($byte1 . $byte2) . chr($finger); 145 | 146 | $self->_command($command, $command_string); 147 | 148 | return !($this->_checkFinger($self, $uid, $finger)); 149 | } 150 | 151 | /** 152 | * @param ZKLib $self 153 | * @param int $uid Unique Employee ID in ZK device 154 | * @param int $finger Finger ID (0-9) 155 | * @return bool Returned true if exist 156 | */ 157 | private function _checkFinger(ZKLib $self, $uid, $finger) 158 | { 159 | $res = $this->_getFinger($self, $uid, $finger); 160 | return (bool)($res['size'] > 0); 161 | } 162 | } -------------------------------------------------------------------------------- /zklib/src/User.php: -------------------------------------------------------------------------------- 1 | _section = __METHOD__; 22 | 23 | if ( 24 | (int)$uid === 0 || 25 | (int)$uid > Util::USHRT_MAX || 26 | strlen($userid) > 9 || 27 | strlen($name) > 24 || 28 | strlen($password) > 8 || 29 | strlen($cardno) > 10 30 | ) { 31 | return false; 32 | } 33 | 34 | $command = Util::CMD_SET_USER; 35 | $byte1 = chr((int)($uid % 256)); 36 | $byte2 = chr((int)($uid >> 8)); 37 | $cardno = hex2bin(Util::reverseHex(dechex($cardno))); 38 | 39 | $command_string = implode('', [ 40 | $byte1, 41 | $byte2, 42 | chr($role), 43 | str_pad($password, 8, chr(0)), 44 | str_pad($name, 24, chr(0)), 45 | str_pad($cardno, 4, chr(0)), 46 | str_pad(chr(1), 9, chr(0)), 47 | str_pad($userid, 9, chr(0)), 48 | str_repeat(chr(0), 15) 49 | ]); 50 | 51 | return $self->_command($command, $command_string); 52 | } 53 | 54 | /** 55 | * @param ZKLib $self 56 | * @return array [userid, name, cardno, uid, role, password] 57 | */ 58 | public function get(ZKLib $self) 59 | { 60 | $self->_section = __METHOD__; 61 | 62 | $command = Util::CMD_USER_TEMP_RRQ; 63 | $command_string = chr(Util::FCT_USER); 64 | 65 | $session = $self->_command($command, $command_string, Util::COMMAND_TYPE_DATA); 66 | if ($session === false) { 67 | return []; 68 | } 69 | 70 | $userData = Util::recData($self); 71 | 72 | $users = []; 73 | if (!empty($userData)) { 74 | $userData = substr($userData, 11); 75 | 76 | while (strlen($userData) > 72) { 77 | $u = unpack('H144', substr($userData, 0, 72)); 78 | 79 | $u1 = hexdec(substr($u[1], 2, 2)); 80 | $u2 = hexdec(substr($u[1], 4, 2)); 81 | $uid = $u1 + ($u2 * 256); 82 | $cardno = hexdec(substr($u[1], 78, 2) . substr($u[1], 76, 2) . substr($u[1], 74, 2) . substr($u[1], 72, 2)) . ' '; 83 | $role = hexdec(substr($u[1], 6, 2)) . ' '; 84 | $password = hex2bin(substr($u[1], 8, 16)) . ' '; 85 | $name = hex2bin(substr($u[1], 24, 74)) . ' '; 86 | $userid = hex2bin(substr($u[1], 98, 72)) . ' '; 87 | 88 | //Clean up some messy characters from the user name 89 | $password = explode(chr(0), $password, 2); 90 | $password = $password[0]; 91 | $userid = explode(chr(0), $userid, 2); 92 | $userid = $userid[0]; 93 | $name = explode(chr(0), $name, 3); 94 | $name = utf8_encode($name[0]); 95 | $cardno = str_pad($cardno, 11, '0', STR_PAD_LEFT); 96 | 97 | if ($name == '') { 98 | $name = $userid; 99 | } 100 | 101 | $users[$userid] = [ 102 | 'userid' => $userid, 103 | 'name' => $name, 104 | 'cardno' => $cardno, 105 | 'uid' => $uid, 106 | 'role' => intval($role), 107 | 'password' => $password 108 | ]; 109 | 110 | $userData = substr($userData, 72); 111 | } 112 | } 113 | 114 | return $users; 115 | } 116 | 117 | /** 118 | * @param ZKLib $self 119 | * @return bool|mixed 120 | */ 121 | public function clear(ZKLib $self) 122 | { 123 | $self->_section = __METHOD__; 124 | 125 | $command = Util::CMD_CLEAR_DATA; 126 | $command_string = ''; 127 | 128 | return $self->_command($command, $command_string); 129 | } 130 | 131 | /** 132 | * @param ZKLib $self 133 | * @return bool|mixed 134 | */ 135 | public function clearAdmin(ZKLib $self) 136 | { 137 | $self->_section = __METHOD__; 138 | 139 | $command = Util::CMD_CLEAR_ADMIN; 140 | $command_string = ''; 141 | 142 | return $self->_command($command, $command_string); 143 | } 144 | 145 | /** 146 | * @param ZKLib $self 147 | * @param integer $uid 148 | * @return bool|mixed 149 | */ 150 | public function remove(ZKLib $self, $uid) 151 | { 152 | $self->_section = __METHOD__; 153 | 154 | $command = Util::CMD_DELETE_USER; 155 | $byte1 = chr((int)($uid % 256)); 156 | $byte2 = chr((int)($uid >> 8)); 157 | $command_string = ($byte1 . $byte2); 158 | 159 | return $self->_command($command, $command_string); 160 | } 161 | } -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | ZK Test 4 | 5 | 6 | 7 | connect(); 19 | if ($ret) { 20 | $zk->disableDevice(); 21 | $zk->setTime(date('Y-m-d H:i:s')); // Synchronize time 22 | ?> 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
StatusConnectedVersionversion()); ?>OS VersionosVersion()); ?>Platformplatform()); ?>
Firmware VersionfmVersion()); ?>WorkCodeworkCode()); ?>SSRssr()); ?>Pin WidthpinWidth()); ?>
Face Function OnfaceFunctionOn()); ?>Serial NumberserialNumber()); ?>Device NamedeviceName()); ?>Get TimegetTime()); ?>
56 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | setUser(1, '1', 'User1', '', ZK\Util::LEVEL_USER); 74 | //$zk->setUser(2, '2', 'User2', '', ZK\Util::LEVEL_USER); 75 | //$zk->setUser(3, '3', 'User3', '', ZK\Util::LEVEL_USER); 76 | //$zk->setUser(5, '5', 'Admin', '1234', ZK\Util::LEVEL_ADMIN); 77 | $users = $zk->getUser(); 78 | sleep(1); 79 | foreach ($users as $uItem) { 80 | ?> 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | clearAdmin(); 96 | //$zk->clearUsers(); 97 | //$zk->removeUser(1); 98 | ?> 99 |
Data User
UIDIDNameCard #RolePassword
 
100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | getAttendance(); 117 | if (count($attendance) > 0) { 118 | $attendance = array_reverse($attendance, true); 119 | sleep(1); 120 | foreach ($attendance as $attItem) { 121 | ?> 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 135 |
Data Attendance
UIDIDNameStateDateTimeType
136 | 0) { 138 | //$zk->clearAttendance(); // Remove attendance log only if not empty 139 | } 140 | ?> 141 | 142 | enableDevice(); 144 | $zk->disconnect(); 145 | } 146 | ?> 147 | 148 | 149 | -------------------------------------------------------------------------------- /zklib/ZKLib.php: -------------------------------------------------------------------------------- 1 | _ip = $ip; 24 | $this->_port = $port; 25 | 26 | $this->_zkclient = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); 27 | 28 | $timeout = ['sec' => 60, 'usec' => 500000]; 29 | socket_set_option($this->_zkclient, SOL_SOCKET, SO_RCVTIMEO, $timeout); 30 | 31 | } 32 | 33 | /** 34 | * Create and send command to device 35 | * 36 | * @param string $command 37 | * @param string $command_string 38 | * @param string $type 39 | * @return bool|mixed 40 | */ 41 | public function _command($command, $command_string, $type = Util::COMMAND_TYPE_GENERAL) 42 | { 43 | $chksum = 0; 44 | $session_id = $this->_session_id; 45 | 46 | $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6/H2h7/H2h8', substr($this->_data_recv, 0, 8)); 47 | $reply_id = hexdec($u['h8'] . $u['h7']); 48 | 49 | $buf = Util::createHeader($command, $chksum, $session_id, $reply_id, $command_string); 50 | 51 | socket_sendto($this->_zkclient, $buf, strlen($buf), 0, $this->_ip, $this->_port); 52 | 53 | try { 54 | @socket_recvfrom($this->_zkclient, $this->_data_recv, 1024, 0, $this->_ip, $this->_port); 55 | 56 | $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6', substr($this->_data_recv, 0, 8)); 57 | 58 | $ret = false; 59 | $session = hexdec($u['h6'] . $u['h5']); 60 | 61 | if ($type === Util::COMMAND_TYPE_GENERAL && $session_id === $session) { 62 | $ret = substr($this->_data_recv, 8); 63 | } else if ($type === Util::COMMAND_TYPE_DATA && !empty($session)) { 64 | $ret = $session; 65 | } 66 | 67 | return $ret; 68 | } catch (ErrorException $e) { 69 | return false; 70 | } catch (Exception $e) { 71 | return false; 72 | } 73 | } 74 | 75 | /** 76 | * Connect to device 77 | * 78 | * @return bool 79 | */ 80 | public function connect() 81 | { 82 | return (new ZK\Connect())->connect($this); 83 | } 84 | 85 | /** 86 | * Disconnect from device 87 | * 88 | * @return bool 89 | */ 90 | public function disconnect() 91 | { 92 | return (new ZK\Connect())->disconnect($this); 93 | } 94 | 95 | /** 96 | * Get device version 97 | * 98 | * @return bool|mixed 99 | */ 100 | public function version() 101 | { 102 | return (new ZK\Version())->get($this); 103 | } 104 | 105 | /** 106 | * Get OS version 107 | * 108 | * @return bool|mixed 109 | */ 110 | public function osVersion() 111 | { 112 | return (new ZK\Os())->get($this); 113 | } 114 | 115 | /** 116 | * Get platform 117 | * 118 | * @return bool|mixed 119 | */ 120 | public function platform() 121 | { 122 | return (new ZK\Platform())->get($this); 123 | } 124 | 125 | /** 126 | * Get firmware version 127 | * 128 | * @return bool|mixed 129 | */ 130 | public function fmVersion() 131 | { 132 | return (new ZK\Platform())->getVersion($this); 133 | } 134 | 135 | /** 136 | * Get work code 137 | * 138 | * @return bool|mixed 139 | */ 140 | public function workCode() 141 | { 142 | return (new ZK\WorkCode())->get($this); 143 | } 144 | 145 | /** 146 | * Get SSR 147 | * 148 | * @return bool|mixed 149 | */ 150 | public function ssr() 151 | { 152 | return (new ZK\Ssr())->get($this); 153 | } 154 | 155 | /** 156 | * Get pin width 157 | * 158 | * @return bool|mixed 159 | */ 160 | public function pinWidth() 161 | { 162 | return (new ZK\Pin())->width($this); 163 | } 164 | 165 | /** 166 | * @return bool|mixed 167 | */ 168 | public function faceFunctionOn() 169 | { 170 | return (new ZK\Face())->on($this); 171 | } 172 | 173 | /** 174 | * Get device serial number 175 | * 176 | * @return bool|mixed 177 | */ 178 | public function serialNumber() 179 | { 180 | return (new ZK\SerialNumber())->get($this); 181 | } 182 | 183 | /** 184 | * Get device name 185 | * 186 | * @return bool|mixed 187 | */ 188 | public function deviceName() 189 | { 190 | return (new ZK\Device())->name($this); 191 | } 192 | 193 | /** 194 | * Disable device 195 | * 196 | * @return bool|mixed 197 | */ 198 | public function disableDevice() 199 | { 200 | return (new ZK\Device())->disable($this); 201 | } 202 | 203 | /** 204 | * Enable device 205 | * 206 | * @return bool|mixed 207 | */ 208 | public function enableDevice() 209 | { 210 | return (new ZK\Device())->enable($this); 211 | } 212 | 213 | /** 214 | * Get users data 215 | * 216 | * @return array [userid, name, cardno, uid, role, password] 217 | */ 218 | public function getUser() 219 | { 220 | return (new ZK\User())->get($this); 221 | } 222 | 223 | /** 224 | * Set user data 225 | * 226 | * @param int $uid Unique ID (max 65535) 227 | * @param int|string $userid ID in DB (same like $uid, max length = 9, only numbers - depends device setting) 228 | * @param string $name (max length = 24) 229 | * @param int|string $password (max length = 8, only numbers - depends device setting) 230 | * @param int $role Default Util::LEVEL_USER 231 | * @param int $cardno Default 0 (max length = 10, only numbers) 232 | * @return bool|mixed 233 | */ 234 | public function setUser($uid, $userid, $name, $password, $role = Util::LEVEL_USER, $cardno = 0) 235 | { 236 | return (new ZK\User())->set($this, $uid, $userid, $name, $password, $role, $cardno); 237 | } 238 | 239 | /** 240 | * Get fingerprint data array by UID 241 | * TODO: Can get data, but don't know how to parse the data. Need more documentation about it... 242 | * 243 | * @param integer $uid Unique ID (max 65535) 244 | * @return array Binary fingerprint data array (where key is finger ID (0-9)) 245 | */ 246 | public function getFingerprint($uid) 247 | { 248 | return (new ZK\Fingerprint())->get($this, $uid); 249 | } 250 | 251 | /** 252 | * Set fingerprint data array 253 | * TODO: Still can not set fingerprint. Need more documentation about it... 254 | * 255 | * @param integer $uid Unique ID (max 65535) 256 | * @param array $data Binary fingerprint data array (where key is finger ID (0-9) same like returned array from 'getFingerprint' method) 257 | * @return int Count of added fingerprints 258 | */ 259 | public function setFingerprint($uid, array $data) 260 | { 261 | return (new ZK\Fingerprint())->set($this, $uid, $data); 262 | } 263 | 264 | /** 265 | * Remove fingerprint by UID and fingers ID array 266 | * 267 | * @param integer $uid Unique ID (max 65535) 268 | * @param array $data Fingers ID array (0-9) 269 | * @return int Count of deleted fingerprints 270 | */ 271 | public function removeFingerprint($uid, array $data) 272 | { 273 | return (new ZK\Fingerprint())->remove($this, $uid, $data); 274 | } 275 | 276 | /** 277 | * Remove All users 278 | * 279 | * @return bool|mixed 280 | */ 281 | public function clearUsers() 282 | { 283 | return (new ZK\User())->clear($this); 284 | } 285 | 286 | /** 287 | * Remove admin 288 | * 289 | * @return bool|mixed 290 | */ 291 | public function clearAdmin() 292 | { 293 | return (new ZK\User())->clearAdmin($this); 294 | } 295 | 296 | /** 297 | * Remove user by UID 298 | * 299 | * @param integer $uid 300 | * @return bool|mixed 301 | */ 302 | public function removeUser($uid) 303 | { 304 | return (new ZK\User())->remove($this, $uid); 305 | } 306 | 307 | /** 308 | * Get attendance log 309 | * 310 | * @return array [uid, id, state, timestamp] 311 | */ 312 | public function getAttendance() 313 | { 314 | return (new ZK\Attendance())->get($this); 315 | } 316 | 317 | /** 318 | * Clear attendance log 319 | * 320 | * @return bool|mixed 321 | */ 322 | public function clearAttendance() 323 | { 324 | return (new ZK\Attendance())->clear($this); 325 | } 326 | 327 | /** 328 | * Set device time 329 | * 330 | * @param string $t Format: "Y-m-d H:i:s" 331 | * @return bool|mixed 332 | */ 333 | public function setTime($t) 334 | { 335 | return (new ZK\Time())->set($this, $t); 336 | } 337 | 338 | /** 339 | * Get device time 340 | * 341 | * @return bool|mixed Format: "Y-m-d H:i:s" 342 | */ 343 | public function getTime() 344 | { 345 | return (new ZK\Time())->get($this); 346 | } 347 | } -------------------------------------------------------------------------------- /zklib/src/Util.php: -------------------------------------------------------------------------------- 1 | (int)date('Y', $timestamp), 77 | 'month' => (int)date('m', $timestamp), 78 | 'day' => (int)date('d', $timestamp), 79 | 'hour' => (int)date('H', $timestamp), 80 | 'minute' => (int)date('i', $timestamp), 81 | 'second' => (int)date('s', $timestamp), 82 | ]; 83 | 84 | $d = (($t->year % 100) * 12 * 31 + (($t->month - 1) * 31) + $t->day - 1) * 85 | (24 * 60 * 60) + ($t->hour * 60 + $t->minute) * 60 + $t->second; 86 | 87 | return $d; 88 | } 89 | 90 | /** 91 | * Decode a timestamp retrieved from the timeclock 92 | * copied from zkemsdk.c - DecodeTime 93 | * 94 | * @param int|string $t 95 | * @return false|string Format: "Y-m-d H:i:s" 96 | */ 97 | static public function decodeTime($t) 98 | { 99 | $second = $t % 60; 100 | $t = $t / 60; 101 | 102 | $minute = $t % 60; 103 | $t = $t / 60; 104 | 105 | $hour = $t % 24; 106 | $t = $t / 24; 107 | 108 | $day = $t % 31 + 1; 109 | $t = $t / 31; 110 | 111 | $month = $t % 12 + 1; 112 | $t = $t / 12; 113 | 114 | $year = floor($t + 2000); 115 | 116 | $d = date('Y-m-d H:i:s', strtotime( 117 | $year . '-' . $month . '-' . $day . ' ' . $hour . ':' . $minute . ':' . $second 118 | )); 119 | 120 | return $d; 121 | } 122 | 123 | /** 124 | * @param string $hex 125 | * @return string 126 | */ 127 | static public function reverseHex($hex) 128 | { 129 | $tmp = ''; 130 | 131 | for ($i = strlen($hex); $i >= 0; $i--) { 132 | $tmp .= substr($hex, $i, 2); 133 | $i--; 134 | } 135 | 136 | return $tmp; 137 | } 138 | 139 | /** 140 | * Checks a returned packet to see if it returned self::CMD_PREPARE_DATA, 141 | * indicating that data packets are to be sent 142 | * Returns the amount of bytes that are going to be sent 143 | * 144 | * @param ZKLib $self 145 | * @return bool|number 146 | */ 147 | static public function getSize(ZKLib $self) 148 | { 149 | $u = unpack('H2h1/H2h2/H2h3/H2h4/H2h5/H2h6/H2h7/H2h8', substr($self->_data_recv, 0, 8)); 150 | $command = hexdec($u['h2'] . $u['h1']); 151 | 152 | if ($command == self::CMD_PREPARE_DATA) { 153 | $u = unpack('H2h1/H2h2/H2h3/H2h4', substr($self->_data_recv, 8, 4)); 154 | $size = hexdec($u['h4'] . $u['h3'] . $u['h2'] . $u['h1']); 155 | return $size; 156 | } else { 157 | return false; 158 | } 159 | } 160 | 161 | /** 162 | * This function calculates the chksum of the packet to be sent to the 163 | * time clock 164 | * Copied from zkemsdk.c 165 | * 166 | * @inheritdoc 167 | */ 168 | static public function createChkSum($p) 169 | { 170 | $l = count($p); 171 | $chksum = 0; 172 | $i = $l; 173 | $j = 1; 174 | while ($i > 1) { 175 | $u = unpack('S', pack('C2', $p['c' . $j], $p['c' . ($j + 1)])); 176 | 177 | $chksum += $u[1]; 178 | 179 | if ($chksum > self::USHRT_MAX) { 180 | $chksum -= self::USHRT_MAX; 181 | } 182 | $i -= 2; 183 | $j += 2; 184 | } 185 | 186 | if ($i) { 187 | $chksum = $chksum + $p['c' . strval(count($p))]; 188 | } 189 | 190 | while ($chksum > self::USHRT_MAX) { 191 | $chksum -= self::USHRT_MAX; 192 | } 193 | 194 | if ($chksum > 0) { 195 | $chksum = -($chksum); 196 | } else { 197 | $chksum = abs($chksum); 198 | } 199 | 200 | $chksum -= 1; 201 | while ($chksum < 0) { 202 | $chksum += self::USHRT_MAX; 203 | } 204 | 205 | return pack('S', $chksum); 206 | } 207 | 208 | /** 209 | * This function puts a the parts that make up a packet together and 210 | * packs them into a byte string 211 | * 212 | * @inheritdoc 213 | */ 214 | static public function createHeader($command, $chksum, $session_id, $reply_id, $command_string) 215 | { 216 | $buf = pack('SSSS', $command, $chksum, $session_id, $reply_id) . $command_string; 217 | 218 | $buf = unpack('C' . (8 + strlen($command_string)) . 'c', $buf); 219 | 220 | $u = unpack('S', self::createChkSum($buf)); 221 | 222 | if (is_array($u)) { 223 | $u = reset($u); 224 | } 225 | $chksum = $u; 226 | 227 | $reply_id += 1; 228 | 229 | if ($reply_id >= self::USHRT_MAX) { 230 | $reply_id -= self::USHRT_MAX; 231 | } 232 | 233 | $buf = pack('SSSS', $command, $chksum, $session_id, $reply_id); 234 | 235 | return $buf . $command_string; 236 | 237 | } 238 | 239 | /** 240 | * Checks a returned packet to see if it returned Util::CMD_ACK_OK, 241 | * indicating success 242 | * 243 | * @inheritdoc 244 | */ 245 | static public function checkValid($reply) 246 | { 247 | $u = unpack('H2h1/H2h2', substr($reply, 0, 8)); 248 | 249 | $command = hexdec($u['h2'] . $u['h1']); 250 | /** TODO: Some device can return 'Connection unauthorized' then should check also */ 251 | if ($command == self::CMD_ACK_OK || $command == self::CMD_ACK_UNAUTH) { 252 | return true; 253 | } else { 254 | return false; 255 | } 256 | } 257 | 258 | /** 259 | * Get User Role string 260 | * @param integer $role 261 | * @return string 262 | */ 263 | static public function getUserRole($role) 264 | { 265 | switch ($role) { 266 | case self::LEVEL_USER: 267 | $ret = 'User'; 268 | break; 269 | case self::LEVEL_ADMIN: 270 | $ret = 'Admin'; 271 | break; 272 | default: 273 | $ret = 'Unknown'; 274 | } 275 | 276 | return $ret; 277 | } 278 | 279 | /** 280 | * Get Attendance State string 281 | * @param integer $state 282 | * @return string 283 | */ 284 | static public function getAttState($state) 285 | { 286 | switch ($state) { 287 | case self::ATT_STATE_FINGERPRINT: 288 | $ret = 'Fingerprint'; 289 | break; 290 | case self::ATT_STATE_PASSWORD: 291 | $ret = 'Password'; 292 | break; 293 | case self::ATT_STATE_CARD: 294 | $ret = 'Card'; 295 | break; 296 | default: 297 | $ret = 'Unknown'; 298 | } 299 | 300 | return $ret; 301 | } 302 | 303 | /** 304 | * Get Attendance Type string 305 | * @param integer $type 306 | * @return string 307 | */ 308 | static public function getAttType($type) 309 | { 310 | switch ($type) { 311 | case self::ATT_TYPE_CHECK_IN: 312 | $ret = 'Check-in'; 313 | break; 314 | case self::ATT_TYPE_CHECK_OUT: 315 | $ret = 'Check-out'; 316 | break; 317 | case self::ATT_TYPE_OVERTIME_IN: 318 | $ret = 'Overtime-in'; 319 | break; 320 | case self::ATT_TYPE_OVERTIME_OUT: 321 | $ret = 'Overtime-out'; 322 | break; 323 | default: 324 | $ret = 'Undefined'; 325 | } 326 | 327 | return $ret; 328 | } 329 | 330 | /** 331 | * Receive data from device 332 | * @param ZKLib $self 333 | * @param int $maxErrors 334 | * @param bool $first if 'true' don't remove first 4 bytes for first row 335 | * @return string 336 | */ 337 | static public function recData(ZKLib $self, $maxErrors = 10, $first = true) 338 | { 339 | $data = ''; 340 | $bytes = self::getSize($self); 341 | 342 | if ($bytes) { 343 | $received = 0; 344 | $errors = 0; 345 | 346 | while ($bytes > $received) { 347 | $ret = @socket_recvfrom($self->_zkclient, $dataRec, 1032, 0, $self->_ip, $self->_port); 348 | 349 | if ($ret === false) { 350 | if ($errors < $maxErrors) { 351 | //try again if false 352 | $errors++; 353 | sleep(1); 354 | continue; 355 | } else { 356 | //return empty if has maximum count of errors 357 | self::logReceived($self, $received, $bytes); 358 | unset($data); 359 | return ''; 360 | } 361 | } 362 | 363 | if ($first === false) { 364 | //The first 4 bytes don't seem to be related to the user 365 | $dataRec = substr($dataRec, 8); 366 | } 367 | 368 | $data .= $dataRec; 369 | $received += strlen($dataRec); 370 | 371 | unset($dataRec); 372 | $first = false; 373 | } 374 | 375 | //flush socket 376 | @socket_recvfrom($self->_zkclient, $dataRec, 1024, 0, $self->_ip, $self->_port); 377 | unset($dataRec); 378 | } 379 | 380 | return $data; 381 | } 382 | 383 | /** 384 | * @param ZKLib $self 385 | * @param int $received 386 | * @param int $bytes 387 | */ 388 | static private function logReceived(ZKLib $self, $received, $bytes) 389 | { 390 | self::logger($self, 'Received: ' . $received . ' of ' . $bytes . ' bytes'); 391 | } 392 | 393 | /** 394 | * Write log 395 | * @param ZKLib $self 396 | * @param string $str 397 | */ 398 | static private function logger(ZKLib $self, $str) 399 | { 400 | if (defined('ZK_LIB_LOG')) { 401 | //use constant if defined 402 | $log = ZK_LIB_LOG; 403 | } else { 404 | $dir = dirname(dirname(__FILE__)); 405 | $log = $dir . '/logs/error.log'; 406 | } 407 | 408 | $row = '<' . $self->_ip . '> [' . date('d.m.Y H:i:s') . '] '; 409 | $row .= (empty($self->_section) ? '' : '(' . $self->_section . ') '); 410 | $row .= $str; 411 | $row .= PHP_EOL; 412 | 413 | file_put_contents($log, $row, FILE_APPEND); 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /zklib/vendor/composer/ClassLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see http://www.php-fig.org/psr/psr-0/ 41 | * @see http://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | 57 | private $classMapAuthoritative = false; 58 | 59 | public function getPrefixes() 60 | { 61 | if (!empty($this->prefixesPsr0)) { 62 | return call_user_func_array('array_merge', $this->prefixesPsr0); 63 | } 64 | 65 | return array(); 66 | } 67 | 68 | public function getPrefixesPsr4() 69 | { 70 | return $this->prefixDirsPsr4; 71 | } 72 | 73 | public function getFallbackDirs() 74 | { 75 | return $this->fallbackDirsPsr0; 76 | } 77 | 78 | public function getFallbackDirsPsr4() 79 | { 80 | return $this->fallbackDirsPsr4; 81 | } 82 | 83 | public function getClassMap() 84 | { 85 | return $this->classMap; 86 | } 87 | 88 | /** 89 | * @param array $classMap Class to filename map 90 | */ 91 | public function addClassMap(array $classMap) 92 | { 93 | if ($this->classMap) { 94 | $this->classMap = array_merge($this->classMap, $classMap); 95 | } else { 96 | $this->classMap = $classMap; 97 | } 98 | } 99 | 100 | /** 101 | * Registers a set of PSR-0 directories for a given prefix, either 102 | * appending or prepending to the ones previously set for this prefix. 103 | * 104 | * @param string $prefix The prefix 105 | * @param array|string $paths The PSR-0 root directories 106 | * @param bool $prepend Whether to prepend the directories 107 | */ 108 | public function add($prefix, $paths, $prepend = false) 109 | { 110 | if (!$prefix) { 111 | if ($prepend) { 112 | $this->fallbackDirsPsr0 = array_merge( 113 | (array) $paths, 114 | $this->fallbackDirsPsr0 115 | ); 116 | } else { 117 | $this->fallbackDirsPsr0 = array_merge( 118 | $this->fallbackDirsPsr0, 119 | (array) $paths 120 | ); 121 | } 122 | 123 | return; 124 | } 125 | 126 | $first = $prefix[0]; 127 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 128 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 129 | 130 | return; 131 | } 132 | if ($prepend) { 133 | $this->prefixesPsr0[$first][$prefix] = array_merge( 134 | (array) $paths, 135 | $this->prefixesPsr0[$first][$prefix] 136 | ); 137 | } else { 138 | $this->prefixesPsr0[$first][$prefix] = array_merge( 139 | $this->prefixesPsr0[$first][$prefix], 140 | (array) $paths 141 | ); 142 | } 143 | } 144 | 145 | /** 146 | * Registers a set of PSR-4 directories for a given namespace, either 147 | * appending or prepending to the ones previously set for this namespace. 148 | * 149 | * @param string $prefix The prefix/namespace, with trailing '\\' 150 | * @param array|string $paths The PSR-4 base directories 151 | * @param bool $prepend Whether to prepend the directories 152 | * 153 | * @throws \InvalidArgumentException 154 | */ 155 | public function addPsr4($prefix, $paths, $prepend = false) 156 | { 157 | if (!$prefix) { 158 | // Register directories for the root namespace. 159 | if ($prepend) { 160 | $this->fallbackDirsPsr4 = array_merge( 161 | (array) $paths, 162 | $this->fallbackDirsPsr4 163 | ); 164 | } else { 165 | $this->fallbackDirsPsr4 = array_merge( 166 | $this->fallbackDirsPsr4, 167 | (array) $paths 168 | ); 169 | } 170 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 171 | // Register directories for a new namespace. 172 | $length = strlen($prefix); 173 | if ('\\' !== $prefix[$length - 1]) { 174 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 175 | } 176 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 177 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 178 | } elseif ($prepend) { 179 | // Prepend directories for an already registered namespace. 180 | $this->prefixDirsPsr4[$prefix] = array_merge( 181 | (array) $paths, 182 | $this->prefixDirsPsr4[$prefix] 183 | ); 184 | } else { 185 | // Append directories for an already registered namespace. 186 | $this->prefixDirsPsr4[$prefix] = array_merge( 187 | $this->prefixDirsPsr4[$prefix], 188 | (array) $paths 189 | ); 190 | } 191 | } 192 | 193 | /** 194 | * Registers a set of PSR-0 directories for a given prefix, 195 | * replacing any others previously set for this prefix. 196 | * 197 | * @param string $prefix The prefix 198 | * @param array|string $paths The PSR-0 base directories 199 | */ 200 | public function set($prefix, $paths) 201 | { 202 | if (!$prefix) { 203 | $this->fallbackDirsPsr0 = (array) $paths; 204 | } else { 205 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 206 | } 207 | } 208 | 209 | /** 210 | * Registers a set of PSR-4 directories for a given namespace, 211 | * replacing any others previously set for this namespace. 212 | * 213 | * @param string $prefix The prefix/namespace, with trailing '\\' 214 | * @param array|string $paths The PSR-4 base directories 215 | * 216 | * @throws \InvalidArgumentException 217 | */ 218 | public function setPsr4($prefix, $paths) 219 | { 220 | if (!$prefix) { 221 | $this->fallbackDirsPsr4 = (array) $paths; 222 | } else { 223 | $length = strlen($prefix); 224 | if ('\\' !== $prefix[$length - 1]) { 225 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 226 | } 227 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 228 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 229 | } 230 | } 231 | 232 | /** 233 | * Turns on searching the include path for class files. 234 | * 235 | * @param bool $useIncludePath 236 | */ 237 | public function setUseIncludePath($useIncludePath) 238 | { 239 | $this->useIncludePath = $useIncludePath; 240 | } 241 | 242 | /** 243 | * Can be used to check if the autoloader uses the include path to check 244 | * for classes. 245 | * 246 | * @return bool 247 | */ 248 | public function getUseIncludePath() 249 | { 250 | return $this->useIncludePath; 251 | } 252 | 253 | /** 254 | * Turns off searching the prefix and fallback directories for classes 255 | * that have not been registered with the class map. 256 | * 257 | * @param bool $classMapAuthoritative 258 | */ 259 | public function setClassMapAuthoritative($classMapAuthoritative) 260 | { 261 | $this->classMapAuthoritative = $classMapAuthoritative; 262 | } 263 | 264 | /** 265 | * Should class lookup fail if not found in the current class map? 266 | * 267 | * @return bool 268 | */ 269 | public function isClassMapAuthoritative() 270 | { 271 | return $this->classMapAuthoritative; 272 | } 273 | 274 | /** 275 | * Registers this instance as an autoloader. 276 | * 277 | * @param bool $prepend Whether to prepend the autoloader or not 278 | */ 279 | public function register($prepend = false) 280 | { 281 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 282 | } 283 | 284 | /** 285 | * Unregisters this instance as an autoloader. 286 | */ 287 | public function unregister() 288 | { 289 | spl_autoload_unregister(array($this, 'loadClass')); 290 | } 291 | 292 | /** 293 | * Loads the given class or interface. 294 | * 295 | * @param string $class The name of the class 296 | * @return bool|null True if loaded, null otherwise 297 | */ 298 | public function loadClass($class) 299 | { 300 | if ($file = $this->findFile($class)) { 301 | includeFile($file); 302 | 303 | return true; 304 | } 305 | } 306 | 307 | /** 308 | * Finds the path to the file where the class is defined. 309 | * 310 | * @param string $class The name of the class 311 | * 312 | * @return string|false The path if found, false otherwise 313 | */ 314 | public function findFile($class) 315 | { 316 | // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 317 | if ('\\' == $class[0]) { 318 | $class = substr($class, 1); 319 | } 320 | 321 | // class map lookup 322 | if (isset($this->classMap[$class])) { 323 | return $this->classMap[$class]; 324 | } 325 | if ($this->classMapAuthoritative) { 326 | return false; 327 | } 328 | 329 | $file = $this->findFileWithExtension($class, '.php'); 330 | 331 | // Search for Hack files if we are running on HHVM 332 | if ($file === null && defined('HHVM_VERSION')) { 333 | $file = $this->findFileWithExtension($class, '.hh'); 334 | } 335 | 336 | if ($file === null) { 337 | // Remember that this class does not exist. 338 | return $this->classMap[$class] = false; 339 | } 340 | 341 | return $file; 342 | } 343 | 344 | private function findFileWithExtension($class, $ext) 345 | { 346 | // PSR-4 lookup 347 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 348 | 349 | $first = $class[0]; 350 | if (isset($this->prefixLengthsPsr4[$first])) { 351 | foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { 352 | if (0 === strpos($class, $prefix)) { 353 | foreach ($this->prefixDirsPsr4[$prefix] as $dir) { 354 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { 355 | return $file; 356 | } 357 | } 358 | } 359 | } 360 | } 361 | 362 | // PSR-4 fallback dirs 363 | foreach ($this->fallbackDirsPsr4 as $dir) { 364 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 365 | return $file; 366 | } 367 | } 368 | 369 | // PSR-0 lookup 370 | if (false !== $pos = strrpos($class, '\\')) { 371 | // namespaced class name 372 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 373 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 374 | } else { 375 | // PEAR-like class name 376 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 377 | } 378 | 379 | if (isset($this->prefixesPsr0[$first])) { 380 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 381 | if (0 === strpos($class, $prefix)) { 382 | foreach ($dirs as $dir) { 383 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 384 | return $file; 385 | } 386 | } 387 | } 388 | } 389 | } 390 | 391 | // PSR-0 fallback dirs 392 | foreach ($this->fallbackDirsPsr0 as $dir) { 393 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 394 | return $file; 395 | } 396 | } 397 | 398 | // PSR-0 include paths. 399 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 400 | return $file; 401 | } 402 | } 403 | } 404 | 405 | /** 406 | * Scope isolated include. 407 | * 408 | * Prevents access to $this/self from included files. 409 | */ 410 | function includeFile($file) 411 | { 412 | include $file; 413 | } 414 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------