diff --git a/assets/knet-icon.png b/assets/knet-icon.png new file mode 100644 index 0000000..5c655f3 Binary files /dev/null and b/assets/knet-icon.png differ diff --git a/assets/knet-logo.png b/assets/knet-logo.png new file mode 100644 index 0000000..fd5645a Binary files /dev/null and b/assets/knet-logo.png differ diff --git a/classes/SimpleXLSXGen.php b/classes/SimpleXLSXGen.php new file mode 100644 index 0000000..ecf272a --- /dev/null +++ b/classes/SimpleXLSXGen.php @@ -0,0 +1,667 @@ +curSheet = -1; + $this->defaultFont = 'Calibri'; + $this->sheets = [ ['name' => 'Sheet1', 'rows' => [], 'hyperlinks' => [] ] ]; + $this->SI = []; // sharedStrings index + $this->SI_KEYS = []; // & keys + $this->F = [ self::F_NORMAL ]; // fonts + $this->F_KEYS = [0]; // & keys + $this->XF = [ [self::N_NORMAL, self::F_NORMAL, self::A_DEFAULT] ]; // styles + $this->XF_KEYS = ['N0F0A0' => 0 ]; // & keys + + $this->template = [ + '_rels/.rels' => ' + + + + +', + 'docProps/app.xml' => ' + +0 +'.__CLASS__.'', + 'docProps/core.xml' => ' + +{DATE} +en-US +{DATE} +1 +', + 'xl/_rels/workbook.xml.rels' => ' + + +{SHEETS}', + 'xl/worksheets/sheet1.xml' => ' +{COLS}{ROWS}{HYPERLINKS}', + 'xl/worksheets/_rels/sheet1.xml.rels' => ' +{HYPERLINKS}', + 'xl/sharedStrings.xml' => ' +{STRINGS}', + 'xl/styles.xml' => ' + +{FONTS} + + + +{XF} + + + +', + 'xl/workbook.xml' => ' + + +{SHEETS} +', + '[Content_Types].xml' => ' + + + + + + + + +{TYPES} +', + ]; + + // + // 01001200 + // Простой шаблонБудем делать генератор + } + public static function fromArray( array $rows, $sheetName = null ) { + $xlsx = new static(); + return $xlsx->addSheet( $rows, $sheetName ); + } + + public function addSheet( array $rows, $name = null ) { + + $this->curSheet++; + if ( $name === null ) { // autogenerated sheet names + $name = 'Sheet'.($this->curSheet+1); + } else { + $names = []; + foreach( $this->sheets as $sh ) { + $names[ mb_strtoupper( $sh['name']) ] = 1; + } + for( $i = 0; $i < 100; $i++ ) { + $new_name = ($i === 0) ? $name : $name .' ('.$i.')'; + $NEW_NAME = mb_strtoupper( $new_name ); + if ( !isset( $names[ $NEW_NAME ]) ) { + $name = $new_name; + break; + } + } + } + + $this->sheets[$this->curSheet] = ['name' => $name, 'hyperlinks' => []]; + + if ( is_array( $rows ) && isset( $rows[0] ) && is_array($rows[0]) ) { + $this->sheets[$this->curSheet]['rows'] = $rows; + } else { + $this->sheets[$this->curSheet]['rows'] = []; + } + return $this; + } + + public function __toString() { + $fh = fopen( 'php://memory', 'wb' ); + if ( ! $fh ) { + return ''; + } + + if ( ! $this->_write( $fh ) ) { + fclose( $fh ); + return ''; + } + $size = ftell( $fh ); + fseek( $fh, 0); + + return (string) fread( $fh, $size ); + } + + public function saveAs( $filename ) { + $fh = fopen( $filename, 'wb' ); + if (!$fh) { + return false; + } + if ( !$this->_write($fh) ) { + fclose($fh); + return false; + } + fclose($fh); + + return true; + } + + public function download() { + return $this->downloadAs( gmdate('YmdHi') . '.xlsx' ); + } + + public function downloadAs( $filename ) { + $fh = fopen('php://memory','wb'); + if (!$fh) { + return false; + } + + if ( !$this->_write( $fh )) { + fclose( $fh ); + return false; + } + + $size = ftell($fh); + + header('Content-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + header('Content-Disposition: attachment; filename="'.$filename.'"'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T' , time() )); + header('Content-Length: '.$size); + + while( ob_get_level() ) { + ob_end_clean(); + } + fseek($fh,0); + fpassthru( $fh ); + + fclose($fh); + return true; + } + + protected function _write( $fh ) { + + + $dirSignatureE= "\x50\x4b\x05\x06"; // end of central dir signature + $zipComments = 'Generated by '.__CLASS__.' PHP class, thanks sergey.shuchkin@gmail.com'; + + if (!$fh) { + return false; + } + + $cdrec = ''; // central directory content + $entries= 0; // number of zipped files + $cnt_sheets = count( $this->sheets ); + + foreach ($this->template as $cfilename => $template ) { + if ( $cfilename === 'xl/_rels/workbook.xml.rels' ) { + $s = ''; + for ( $i = 0; $i < $cnt_sheets; $i++) { + $s .= '\n"; + } + $s .= ''; + $template = str_replace('{SHEETS}', $s, $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } elseif ( $cfilename === 'xl/workbook.xml' ) { + $s = ''; + foreach ( $this->sheets as $k => $v ) { + $s .= ''; + } + $template = str_replace('{SHEETS}', $s, $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } + elseif ( $cfilename === 'docProps/core.xml' ) { + $template = str_replace('{DATE}', gmdate('Y-m-d\TH:i:s\Z'), $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } elseif ( $cfilename === 'xl/sharedStrings.xml' ) { + if (!count($this->SI)) { + $this->SI[] = 'No Data'; + } + $si_cnt = count($this->SI); + $si = ''.implode("\r\n", $this->SI).''; + $template = str_replace(['{CNT}', '{STRINGS}'], [ $si_cnt, $si ], $template ); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } elseif ( $cfilename === 'xl/worksheets/sheet1.xml' ) { + foreach ( $this->sheets as $k => $v ) { + $filename = 'xl/worksheets/sheet'.($k+1).'.xml'; + $xml = $this->_sheetToXML($k, $template); + $this->_writeEntry($fh, $cdrec, $filename, $xml ); + $entries++; + } + $xml = null; + } elseif ( $cfilename === 'xl/worksheets/_rels/sheet1.xml.rels' ) { + foreach ( $this->sheets as $k => $v ) { + if ( count($v['hyperlinks'])) { + $RH = []; + $filename = 'xl/worksheets/_rels/sheet' . ( $k + 1 ) . '.xml.rels'; + foreach ( $v['hyperlinks'] as $h ) { + $RH[] = ''; + } + $xml = str_replace( '{HYPERLINKS}', implode( "\r\n", $RH ), $template ); + $this->_writeEntry( $fh, $cdrec, $filename, $xml ); + $entries++; + } + } + $xml = null; + + } elseif ( $cfilename === '[Content_Types].xml' ) { + $TYPES = ['']; + foreach ( $this->sheets as $k => $v) { + $TYPES[] = ''; + if (count( $v['hyperlinks'])) { + $TYPES[] = ''; + } + } + $template = str_replace('{TYPES}', implode("\r\n", $TYPES), $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } elseif ( $cfilename === 'xl/styles.xml' ) { + $FONTS = ['']; + foreach ( $this->F as $f ) { + $FONTS[] = '' + . ( $this->defaultFontSize ? '' : '' ) + .( $f & self::F_BOLD ? '' : '') + .( $f & self::F_ITALIC ? '' : '') + .( $f & self::F_UNDERLINE ? '' : '') + .( $f & self::F_STRIKE ? '' : '') + .( $f & self::F_HYPERLINK ? '' : '') + .''; + } + $FONTS[] = ''; + $XF = ['']; + foreach( $this->XF as $xf ) { + $align = ($xf[2] === self::A_LEFT ? ' applyAlignment="1">' : '') + .($xf[2] === self::A_RIGHT ? ' applyAlignment="1">' : '') + .($xf[2] === self::A_CENTER ? ' applyAlignment="1">' : ''); + $XF[] = ' 0 ? ' applyNumberFormat="1"' : '') + .($align ? $align . '' : '/>'); + + } + $XF[] = ''; + $template = str_replace(['{FONTS}','{XF}'], [implode("\r\n", $FONTS), implode("\r\n", $XF)], $template); + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } else { + $this->_writeEntry($fh, $cdrec, $cfilename, $template); + $entries++; + } + } + $before_cd = ftell($fh); + fwrite($fh, $cdrec); + + // end of central dir + fwrite($fh, $dirSignatureE); + fwrite($fh, pack('v', 0)); // number of this disk + fwrite($fh, pack('v', 0)); // number of the disk with the start of the central directory + fwrite($fh, pack('v', $entries)); // total # of entries "on this disk" + fwrite($fh, pack('v', $entries)); // total # of entries overall + fwrite($fh, pack('V', mb_strlen($cdrec,'8bit'))); // size of central dir + fwrite($fh, pack('V', $before_cd)); // offset to start of central dir + fwrite($fh, pack('v', mb_strlen($zipComments,'8bit'))); // .zip file comment length + fwrite($fh, $zipComments); + + return true; + } + + protected function _writeEntry($fh, &$cdrec, $cfilename, $data) { + $zipSignature = "\x50\x4b\x03\x04"; // local file header signature + $dirSignature = "\x50\x4b\x01\x02"; // central dir header signature + + $e = []; + $e['uncsize'] = mb_strlen($data, '8bit'); + + // if data to compress is too small, just store it + if($e['uncsize'] < 256){ + $e['comsize'] = $e['uncsize']; + $e['vneeded'] = 10; + $e['cmethod'] = 0; + $zdata = $data; + } else{ // otherwise, compress it + $zdata = gzcompress($data); + $zdata = substr(substr($zdata, 0, - 4 ), 2); // fix crc bug (thanks to Eric Mueller) + $e['comsize'] = mb_strlen($zdata, '8bit'); + $e['vneeded'] = 10; + $e['cmethod'] = 8; + } + + $e['bitflag'] = 0; + $e['crc_32'] = crc32($data); + + // Convert date and time to DOS Format, and set then + $lastmod_timeS = str_pad(decbin(date('s')>=32?date('s')-32:date('s')), 5, '0', STR_PAD_LEFT); + $lastmod_timeM = str_pad(decbin(date('i')), 6, '0', STR_PAD_LEFT); + $lastmod_timeH = str_pad(decbin(date('H')), 5, '0', STR_PAD_LEFT); + $lastmod_dateD = str_pad(decbin(date('d')), 5, '0', STR_PAD_LEFT); + $lastmod_dateM = str_pad(decbin(date('m')), 4, '0', STR_PAD_LEFT); + $lastmod_dateY = str_pad(decbin(date('Y')-1980), 7, '0', STR_PAD_LEFT); + + # echo "ModTime: $lastmod_timeS-$lastmod_timeM-$lastmod_timeH (".date("s H H").")\n"; + # echo "ModDate: $lastmod_dateD-$lastmod_dateM-$lastmod_dateY (".date("d m Y").")\n"; + $e['modtime'] = bindec("$lastmod_timeH$lastmod_timeM$lastmod_timeS"); + $e['moddate'] = bindec("$lastmod_dateY$lastmod_dateM$lastmod_dateD"); + + $e['offset'] = ftell($fh); + + fwrite($fh, $zipSignature); + fwrite($fh, pack('s', $e['vneeded'])); // version_needed + fwrite($fh, pack('s', $e['bitflag'])); // general_bit_flag + fwrite($fh, pack('s', $e['cmethod'])); // compression_method + fwrite($fh, pack('s', $e['modtime'])); // lastmod_time + fwrite($fh, pack('s', $e['moddate'])); // lastmod_date + fwrite($fh, pack('V', $e['crc_32'])); // crc-32 + fwrite($fh, pack('I', $e['comsize'])); // compressed_size + fwrite($fh, pack('I', $e['uncsize'])); // uncompressed_size + fwrite($fh, pack('s', mb_strlen($cfilename, '8bit'))); // file_name_length + fwrite($fh, pack('s', 0)); // extra_field_length + fwrite($fh, $cfilename); // file_name + // ignoring extra_field + fwrite($fh, $zdata); + + // Append it to central dir + $e['external_attributes'] = (substr($cfilename, -1) === '/'&&!$zdata)?16:32; // Directory or file name + $e['comments'] = ''; + + $cdrec .= $dirSignature; + $cdrec .= "\x0\x0"; // version made by + $cdrec .= pack('v', $e['vneeded']); // version needed to extract + $cdrec .= "\x0\x0"; // general bit flag + $cdrec .= pack('v', $e['cmethod']); // compression method + $cdrec .= pack('v', $e['modtime']); // lastmod time + $cdrec .= pack('v', $e['moddate']); // lastmod date + $cdrec .= pack('V', $e['crc_32']); // crc32 + $cdrec .= pack('V', $e['comsize']); // compressed filesize + $cdrec .= pack('V', $e['uncsize']); // uncompressed filesize + $cdrec .= pack('v', mb_strlen($cfilename,'8bit')); // file name length + $cdrec .= pack('v', 0); // extra field length + $cdrec .= pack('v', mb_strlen($e['comments'],'8bit')); // file comment length + $cdrec .= pack('v', 0); // disk number start + $cdrec .= pack('v', 0); // internal file attributes + $cdrec .= pack('V', $e['external_attributes']); // internal file attributes + $cdrec .= pack('V', $e['offset']); // relative offset of local header + $cdrec .= $cfilename; + $cdrec .= $e['comments']; + } + + protected function _sheetToXML($idx, $template) { + // locale floats fr_FR 1.234,56 -> 1234.56 + $_loc = setlocale(LC_NUMERIC, 0); + setlocale(LC_NUMERIC,'C'); + $COLS = []; + $ROWS = []; + if ( count($this->sheets[$idx]['rows']) ) { + $COLS[] = ''; + $CUR_ROW = 0; + $COL = []; + foreach( $this->sheets[$idx]['rows'] as $r ) { + $CUR_ROW++; + $row = ''; + $CUR_COL = 0; + foreach( $r as $v ) { + $CUR_COL++; + if ( !isset($COL[ $CUR_COL ])) { + $COL[ $CUR_COL ] = 0; + } + if ( $v === null || $v === '' ) { + continue; + } + + $cname = $this->num2name($CUR_COL) . $CUR_ROW; + + $ct = $cv = null; + $N = $F = $A = 0; + + if ( is_string($v) ) { + + if ( $v[0] === "\0" ) { // RAW value as string + $v = substr($v,1); + $vl = mb_strlen( $v ); + } else { + if ( strpos( $v, '<' ) !== false ) { // tags? + if ( strpos( $v, '' ) !== false ) { + $F += self::F_BOLD; + } + if ( strpos( $v, '' ) !== false ) { + $F += self::F_ITALIC; + } + if ( strpos( $v, '' ) !== false ) { + $F += self::F_UNDERLINE; + } + if ( strpos( $v, '' ) !== false ) { + $F += self::F_STRIKE; + } + if ( strpos( $v, '' ) !== false ) { + $A += self::A_LEFT; + } + if ( strpos( $v, '
' ) !== false ) { + $A += self::A_CENTER; + } + if ( strpos( $v, '' ) !== false ) { + $A += self::A_RIGHT; + } + if ( preg_match( '/(.*?)<\/a>/i', $v, $m ) ) { + $h = explode( '#', $m[1] ); + $this->sheets[ $idx ]['hyperlinks'][] = ['ID' => 'rId' . ( count( $this->sheets[ $idx ]['hyperlinks'] ) + 1 ), 'R' => $cname, 'H' => $h[0], 'L' => isset( $h[1] ) ? $h[1] : '']; + $F = self::F_HYPERLINK; // Hyperlink + } + if ( preg_match( '/(.*?)<\/a>/i', $v, $m ) ) { + $this->sheets[ $idx ]['hyperlinks'][] = ['ID' => 'rId' . ( count( $this->sheets[ $idx ]['hyperlinks'] ) + 1 ), 'R' => $cname, 'H' => $m[1], 'L' => '']; + $F = self::F_HYPERLINK; // mailto hyperlink + } + $v = strip_tags( $v ); + } // tags + $vl = mb_strlen( $v ); + if ( $v === '0' || preg_match( '/^[-+]?[1-9]\d{0,14}$/', $v ) ) { // Integer as General + $cv = ltrim( $v, '+' ); + if ( $vl > 10 ) { + $N = self::N_INT; // [1] 0 + } + } elseif ( preg_match( '/^[-+]?(0|[1-9]\d*)\.(\d+)$/', $v, $m ) ) { + $cv = ltrim( $v, '+' ); + if ( strlen( $m[2] ) < 3 ) { + $N = self::N_DEC; + } + } elseif ( preg_match( '/^([-+]?\d+)%$/', $v, $m ) ) { + $cv = round( $m[1] / 100, 2 ); + $N = self::N_PERCENT_INT; // [9] 0% + } elseif ( preg_match( '/^([-+]?\d+\.\d+)%$/', $v, $m ) ) { + $cv = round( $m[1] / 100, 4 ); + $N = self::N_PRECENT_DEC; // [10] 0.00% + } elseif ( preg_match( '/^(\d\d\d\d)-(\d\d)-(\d\d)$/', $v, $m ) ) { + $cv = $this->date2excel( $m[1], $m[2], $m[3] ); + $N = self::N_DATE; // [14] mm-dd-yy + } elseif ( preg_match( '/^(\d\d)\/(\d\d)\/(\d\d\d\d)$/', $v, $m ) ) { + $cv = $this->date2excel( $m[3], $m[2], $m[1] ); + $N = self::N_DATE; // [14] mm-dd-yy + } elseif ( preg_match( '/^(\d\d):(\d\d):(\d\d)$/', $v, $m ) ) { + $cv = $this->date2excel( 0, 0, 0, $m[1], $m[2], $m[3] ); + $N = self::N_TIME; // time + } elseif ( preg_match( '/^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/', $v, $m ) ) { + $cv = $this->date2excel( $m[1], $m[2], $m[3], $m[4], $m[5], $m[6] ); + $N = self::N_DATETIME; // [22] m/d/yy h:mm + } elseif ( preg_match( '/^(\d\d)\/(\d\d)\/(\d\d\d\d) (\d\d):(\d\d):(\d\d)$/', $v, $m ) ) { + $cv = $this->date2excel( $m[3], $m[2], $m[1], $m[4], $m[5], $m[6] ); + $N = self::N_DATETIME; // [22] m/d/yy h:mm + } elseif ( preg_match( '/^[0-9+-.]+$/', $v ) ) { // Long ? + $A = self::A_RIGHT; + } elseif ( preg_match( '/^https?:\/\/\S+$/i', $v ) ) { + $h = explode( '#', $v ); + $this->sheets[ $idx ]['hyperlinks'][] = ['ID' => 'rId' . ( count( $this->sheets[ $idx ]['hyperlinks'] ) + 1 ), 'R' => $cname, 'H' => $h[0], 'L' => isset( $h[1] ) ? $h[1] : '']; + $F = self::F_HYPERLINK; // Hyperlink + } elseif ( preg_match( "/^[a-zA-Z0-9_\.\-]+@([a-zA-Z0-9][a-zA-Z0-9\-]*\.)+[a-zA-Z]{2,}$/", $v ) ) { + $this->sheets[ $idx ]['hyperlinks'][] = ['ID' => 'rId' . ( count( $this->sheets[ $idx ]['hyperlinks'] ) + 1 ), 'R' => $cname, 'H' => 'mailto:' . $v, 'L' => '']; + $F = self::F_HYPERLINK; // Hyperlink + } + } + if ( !$cv) { + + $v = $this->esc( $v ); + + if ( mb_strlen( $v ) > 160 ) { + $ct = 'inlineStr'; + $cv = $v; + } else { + $ct = 's'; // shared string + $cv = false; + $skey = '~' . $v; + if ( isset( $this->SI_KEYS[ $skey ] ) ) { + $cv = $this->SI_KEYS[ $skey ]; + } + if ( $cv === false ) { + $this->SI[] = $v; + $cv = count( $this->SI ) - 1; + $this->SI_KEYS[ $skey ] = $cv; + } + } + } + } elseif ( is_int( $v ) ) { + $vl = mb_strlen( (string) $v ); + $cv = $v; + } elseif ( is_float( $v ) ) { + $vl = mb_strlen( (string) $v ); + $cv = $v; + } elseif ( $v instanceof DateTime ) { + $vl = 16; + $cv = $this->date2excel( $v->format('Y'), $v->format('m'), $v->format('d'), $v->format('H'), $v->format('i'), $v->format('s') ); + $N = self::N_DATETIME; // [22] m/d/yy h:mm + } else { + continue; + } + + $COL[ $CUR_COL ] = max( $vl, $COL[ $CUR_COL ] ); + + $cs = 0; + if ( $N + $F + $A > 0 ) { + + if ( isset($this->F_KEYS[ $F ] ) ) { + $cf = $this->F_KEYS[ $F ]; + } else { + $cf = count($this->F); + $this->F_KEYS[$F] = $cf; + $this->F[] = $F; + } + $NFA = 'N' . $N . 'F' . $cf . 'A' . $A; + if ( isset( $this->XF_KEYS[ $NFA ] ) ) { + $cs = $this->XF_KEYS[ $NFA ]; + } + if ( $cs === 0 ) { + $cs = count( $this->XF ); + $this->XF_KEYS[ $NFA ] = $cs; + $this->XF[] = [$N, $cf, $A]; + } + } + + $row .= '' + .($ct === 'inlineStr' ? ''.$cv.'' : '' . $cv . '')."\r\n"; + } + $ROWS[] = $row . "\r\n"; + } + foreach ( $COL as $k => $max ) { + $COLS[] = ''; + } + $COLS[] = ''; + $REF = 'A1:'.$this->num2name(count($COLS)) . $CUR_ROW; + } else { + $ROWS[] = '0'; + $REF = 'A1:A1'; + } + $HYPERLINKS = []; + if ( count( $this->sheets[$idx]['hyperlinks']) ) { + $HYPERLINKS[] = ''; + foreach ( $this->sheets[$idx]['hyperlinks'] as $h ) { + $HYPERLINKS[] = ''; + } + $HYPERLINKS[] = ''; + } + //restore locale + setlocale(LC_NUMERIC, $_loc); + + return str_replace(['{REF}','{COLS}','{ROWS}','{HYPERLINKS}'], + [ $REF, implode("\r\n", $COLS), implode("\r\n",$ROWS), implode("\r\n", $HYPERLINKS) ], + $template ); + } + + public function num2name($num) { + $numeric = ($num - 1) % 26; + $letter = chr( 65 + $numeric ); + $num2 = (int) ( ($num-1) / 26 ); + if ( $num2 > 0 ) { + return $this->num2name( $num2 ) . $letter; + } + return $letter; + } + + public function date2excel($year, $month, $day, $hours=0, $minutes=0, $seconds=0) { + $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400; + + if ( $year === 0 ) { + return $excelTime; + } + + // self::CALENDAR_WINDOWS_1900 + $excel1900isLeapYear = True; + if (((int)$year === 1900) && ($month <= 2)) { $excel1900isLeapYear = False; } + $myExcelBaseDate = 2415020; + + // Julian base date Adjustment + if ($month > 2) { + $month -= 3; + } else { + $month += 9; + --$year; + } + // Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0) + $century = substr($year,0,2); + $decade = substr($year,2,2); + $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myExcelBaseDate + $excel1900isLeapYear; + + return (float) $excelDate + $excelTime; + } + public function setDefaultFont( $name ) { + $this->defaultFont = $name; + return $this; + } + public function setDefaultFontSize( $size ) { + $this->defaultFontSize = $size; + return $this; + } + public function esc( $str ) { + // XML UTF-8: #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + // but we use fast version + return str_replace( ['&', '<', '>', "\x00","\x03","\x0B"], ['&', '<', '>', '', '', ''], $str ); + } +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..fb49f88 --- /dev/null +++ b/index.php @@ -0,0 +1,676 @@ +init_gateway(); + $this->init_form_fields(); + $this->init_settings(); + + $this->title = $this->get_option('title'); + $this->description = $this->get_option('description'); + $this->tranportal_id = $this->get_option('tranportal_id'); + $this->password = $this->get_option('password'); + $this->resource_key = $this->get_option('resource_key'); + $this->is_test = $this->get_option('is_test'); + if($this->is_test == "no") + { + $this->GatewayUrl = "https://kpay.com.kw/"; + } + add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) ); + add_filter('woocommerce_thankyou_order_received_text', [$this,'wc_woo_change_order_received_text'] ); + add_filter( 'woocommerce_endpoint_order-received_title', [$this,'wc_thank_you_title']); + + // add routers + //add_action('init', [$this,'wc_knet_rewrite_tag_rule'], 10, 0); + } + + public function wc_woo_change_order_received_text($str) { + global $id; + $order_status = $this->get_order_in_recived_page($id); + return sprintf("%s %s.",__("Thank you. Your order has been","wc_knet"),$this->get_status_color($order_status),__(ucfirst($order_status),"woocommerce")); + } + + public function wc_thank_you_title( $old_title){ + global $id; + $order_status = $this->get_order_in_recived_page($id); + + if ( isset ( $order_status ) ) { + return sprintf( "%s , %s",__('Order',"wc_knet"),$this->get_status_color($order_status), esc_html( __(ucfirst($order_status),"woocommerce")) ); + } + return $old_title; + } + + private function get_order_in_recived_page($page_id){ + global $wp; + if ( is_order_received_page() && get_the_ID() === $page_id ) { + $order_id = apply_filters( 'woocommerce_thankyou_order_id', absint( $wp->query_vars['order-received'] ) ); + $order_key = apply_filters( 'woocommerce_thankyou_order_key', empty( $_GET['key'] ) ? '' : wc_clean( $_GET['key'] ) ); + if ( $order_id > 0 ) { + $order = new WC_Order( $order_id ); + + if ( $order->get_order_key() != $order_key ) { + $order = false; + } + return $order->get_status(); + } + } + return false; + } + + /** + * set status color + * @param $status + * @return string + */ + private function get_status_color($status){ + switch ($status){ + case "pending": + return "#0470fb"; + case "processing": + return "#fbbd04"; + case "on-hold": + return "#04c1fb"; + case "completed": + return "green"; + default: + return "#fb0404"; + } + } + + /** + * define knet route like knetresponce/success + */ + /*public function wc_knet_rewrite_tag_rule() { + add_rewrite_rule( '^knetresponce/([^/]*)/?', 'index.php?knetresponce=$matches[1]','top' ); + }*/ + /** + * initialization gateway call default data + * like id,icon + */ + public function init_gateway() + { + $this->id = 'wc_knet'; + $this->icon = plugins_url( 'assets/knet-logo.png' , __FILE__ ); + $this->method_title = __('Knet', 'wc_knet'); + $this->method_description = __( 'intgration with knet php raw.', 'woocommerce' ); + $this->has_fields = true; + } + /** + * Define Form Option fields + * - Options for payment like 'title', 'description', 'tranportal_id', 'password', 'resource_key' + **/ + public function init_form_fields() + { + $this->form_fields = array( + 'enabled' => array( + 'title' => __( 'Enable/Disable', 'woocommerce' ), + 'type' => 'checkbox', + 'label' => __( 'Enable Knet Payment', 'woocommerce' ), + 'default' => 'yes' + ), + 'is_test' => array( + 'title' => 'Test mode', + 'label' => 'Enable Test Mode', + 'type' => 'checkbox', + 'description' => 'Place the payment gateway in test mode using test API keys.', + 'default' => 'no', + 'desc_tip' => true, + ), + 'title' => array( + 'title' => __( 'Title', 'woocommerce' ), + 'type' => 'text', + 'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce' ), + 'default' => __( 'knet', 'woocommerce' ), + 'desc_tip' => true, + ), + 'description' => array( + 'title' => __( 'Description', 'woocommerce' ), + 'type' => 'textarea', + 'default' => '' + ), + 'tranportal_id' => array( + 'title' => __( 'Tranportal Id', 'wc_knet' ), + 'type' => 'text', + 'label' => __( 'Necessary data requested from the bank ', 'wc_knet' ), + 'default' => '' + ), + 'password' => array( + 'title' => __( 'Transportal Password', 'wc_knet' ), + 'type' => 'password', + 'description' => __( 'Necessary data requested from the bank ', 'wc_knet' ), + 'default' => '', + 'desc_tip' => true, + ), + 'resource_key' => array( + 'title' => __( 'Terminal Resource Key', 'wc_knet' ), + 'type' => 'password', + 'description' => __( 'Necessary data requested from the bank', 'wc_knet' ), + 'default' => '', + 'desc_tip' => true, + ), + + ); + } + /** + * Admin Panel Options + * - Options for bits like 'title', 'description', 'alias' + **/ + public function admin_options(){ + echo '

'.__('Knet', 'wc_knet').'

'; + echo '

'.__('Knet', 'wc_knet').'

'; + echo ''; + $this->generate_settings_html(); + echo '
'; + + } + /** + * Process payment + * return array + * status,pay url + * 1- get request data (pay url) + * 2- Mark as on-hold (we're awaiting the cheque) + * 3- Remove cart + * 4- Return thankyou redirect + * 5- or failed pay + */ + function process_payment( $order_id ) + { + + global $woocommerce; + $order = new WC_Order( $order_id ); + if(!$order->get_id()) + { + wc_add_notice( __("Order not found", "wc_knet"), 'error' ); + return array( + 'result' => 'error', + 'redirect' => $this->get_return_url( $order ) + ); + } + //get request data (pay url) + $request = $this->request($order); + + // Mark as on-hold (we're awaiting the cheque) + $order->update_status('on-hold', __( 'Awaiting cheque payment', 'woocommerce' )); + + // Remove cart + //$woocommerce->cart->empty_cart(); + + if(!empty($request)) + { + // Return thankyou redirect + return array( + 'result' => 'success', + 'redirect' => $request['url'] + ); + } + else + { + $order->add_order_note( __('Payment error:', 'woothemes') . __("Knet can't get data", 'wc_knet'),'error' ); + $order->update_status('failed'); + return array( + 'result' => 'error', + 'redirect' => $this->get_return_url( $order ) + ); + } + } + + /** + * return pay url to rediredt to knet gateway web site + * return array + */ + public function request($order) + { + $this->formatUrlParames($order); + $param = $this->encryptAES($this->paymentUrl,$this->resource_key)."&tranportalId=".$this->tranportal_id."&responseURL=".$this->responseURL."&errorURL=".$this->errorURL; + $payURL= $this->GatewayUrl."kpg/PaymentHTTP.htm?param=paymentInit"."&trandata=".$param; + return [ + 'status' => 'success', + 'url' =>$payURL, + 'payment_id' => $this->trackId + ]; + } + /** + * prepare pay url parames to kent + * this update pay url var + */ + private function formatUrlParames($order) + { + $this->user_id = $order->get_user_id(); + if($this->user_id) + { + $user_info = $order->get_user(); + $this->name = $user_info->user_login; + $this->email = $user_info->user_email; + $this->mobile = $user_info->user_phone; + } + $this->errorURL = get_site_url()."/index.php?knetresponce=success"; + $this->responseURL = get_site_url()."/index.php?knetresponce=success"; + + $this->trackId = time().mt_rand(1000,100000); + $replace_array = array(); + $replace_array['{id}'] = $this->tranportal_id; + $replace_array['{password}'] = $this->password; + $replace_array['{amt}'] = $order->get_total(); + $replace_array['{trackid}'] = $this->trackId; + $replace_array['{responseURL}'] = $this->responseURL; + $replace_array['{errorURL}'] = $this->errorURL; + + $replace_array['{udf1}'] = $order->get_id(); + $replace_array['{udf2}'] = $this->name; + $replace_array['{udf3}'] =$this->email; + $replace_array['{udf4}'] = $this->mobile; + $replace_array['{udf5}'] = ''; + $this->paymentUrl = str_replace(array_keys($replace_array),array_values($replace_array),$this->paymentUrl); + } + /** + * update order after responce Done from knet + * return string + * url for order view + */ + public function updateOrder() + { + // defince rexpoce data + $resnopseData = $this->responce(); + + if($resnopseData) + { + + $order_id = $resnopseData["udf1"]; + $status = $resnopseData["status"]; + $tranid = $resnopseData["tranid"]; + $ref = $resnopseData["ref"]; + $paymentid = $resnopseData["paymentid"]; + $trackid = $resnopseData["trackid"]; + $result = $resnopseData["result"]; + $ErrorText = $resnopseData["ErrorText"]; + $Error = $resnopseData["Error"]; + $order = new WC_Order( $order_id ); + $transation_data = [ + "payment_id"=> $paymentid, + "track_id"=>$trackid, + "tran_id"=>$tranid, + "ref_id"=>$ref, + "result"=>$result, + 'status' => ($result == "CAPTURED") ? STATUS_SUCCESS : STATUS_FAIL, + "amount" => $resnopseData["ammount"], + "data" => $resnopseData["data"], + 'error'=>$ErrorText, + ]; + if(!$order->get_id()) + { + wc_add_notice( __("Order not found", "wc_knet"), 'error' ); + return $order->get_view_order_url(); + } + elseif(isset($status) && $status == "success") + { + + switch ($result) { + case 'CAPTURED': + $order->payment_complete(); + $order->update_status('completed'); + break; + case 'NOT CAPTURED': + $order->update_status('refunded'); + break; + case 'CANCELED': + $order->update_status('cancelled'); + break; + default: + $order->update_status('refunded'); + break; + } + + $knetInfomation = ""; + $knetInfomation.= __('Result', 'woothemes')." : $result\n"; + $knetInfomation.= __('Payment id', 'woothemes')." : $paymentid\n"; + $knetInfomation.= __('track id', 'woothemes')." : $trackid\n"; + $knetInfomation.= __('Transaction id', 'woothemes')." : $tranid\n"; + $knetInfomation.= __('Refrance id', 'woothemes')." : $ref\n"; + $order->add_order_note($knetInfomation); + // insert transation + do_action("wc_knet_create_new_transation",$order,$transation_data); + } + elseif(isset($status) && $status == "error") + { + $knetInfomation = ""; + $knetInfomation.= __('Result', 'woothemes')." : $result\n"; + $knetInfomation.= __('Payment id', 'woothemes')." : $paymentid\n"; + $knetInfomation.= __('track id', 'woothemes')." : $trackid\n"; + $knetInfomation.= __('Transaction id', 'woothemes')." : $tranid\n"; + $knetInfomation.= __('Refrance id', 'woothemes')." : $ref\n"; + $knetInfomation.= __('Error', 'woothemes')." : $Error\n"; + $knetInfomation.= __('Error Message', 'woothemes')." : $ErrorText\n"; + $order->add_order_note($knetInfomation); + $order->update_status('refunded'); + + // insert transation + do_action("wc_knet_create_new_transation",$order,$transation_data); + } + } + return $this->get_return_url($order); + } + /** + * get responce came from kney payment + * return array() + */ + private function responce() + { + $ResErrorText = (isset($_REQUEST['ErrorText'])) ? sanitize_text_field($_REQUEST['ErrorText']) : null; //Error Text/message + $ResPaymentId = (isset($_REQUEST['paymentid'])) ? sanitize_text_field($_REQUEST['paymentid']) : null; //Payment Id + $ResTrackID = (isset($_REQUEST['trackid'])) ? sanitize_text_field($_REQUEST['trackid']) : null; //Merchant Track ID + $ResErrorNo = (isset($_REQUEST['Error'])) ? sanitize_text_field($_REQUEST['Error']) : null; //Error Number + $ResResult = (isset($_REQUEST['result'])) ? sanitize_text_field($_REQUEST['result']) : null; //Transaction Result + $ResPosdate = (isset($_REQUEST['postdate'])) ? sanitize_text_field($_REQUEST['postdate']) : null; //Postdate + $ResTranId = (isset($_REQUEST['tranid'])) ? sanitize_text_field($_REQUEST['tranid']) : null; //Transaction ID + $ResAuth = (isset($_REQUEST['auth'])) ? sanitize_text_field($_REQUEST['auth']) : null; //Auth Code + $ResAVR = (isset($_REQUEST['avr'])) ? sanitize_text_field($_REQUEST['avr']) : null; //TRANSACTION avr + $ResRef = (isset($_REQUEST['ref'])) ? sanitize_text_field($_REQUEST['ref']) : null; //Reference Number also called Seq Number + $ResAmount = (isset($_REQUEST['amt'])) ? sanitize_text_field($_REQUEST['amt']) : null; //Transaction Amount + $Resudf1 = (isset($_REQUEST['udf1'])) ? sanitize_text_field($_REQUEST['udf1']) : null; //UDF1 + $Resudf2 = (isset($_REQUEST['udf2'])) ? sanitize_text_field($_REQUEST['udf2']) : null; //UDF2 + $Resudf3 = (isset($_REQUEST['udf3'])) ? sanitize_text_field($_REQUEST['udf3']) : null; //UDF3 + $Resudf4 = (isset($_REQUEST['udf4'])) ? sanitize_text_field($_REQUEST['udf4']) : null; //UDF4 + $Resudf5 = (isset($_REQUEST['udf5'])) ? sanitize_text_field($_REQUEST['udf5']) : null; //UDF5 + if($ResErrorText==null && $ResErrorNo==null && $ResPaymentId != null) + { + // success + $ResTranData= (isset($_REQUEST['trandata'])) ? sanitize_text_field($_REQUEST['trandata']) : null; + $decrytedData=$this->decrypt($ResTranData,$this->resource_key); + parse_str($decrytedData, $output); + + if($ResTranData !=null) + { + $result['status'] = 'success'; + $result['paymentid'] = $ResPaymentId; + $result['trackid'] = $ResTrackID; + $result['tranid'] = $output['tranid']; + $result['ref'] = $output['ref']; + $result['result'] = $output['result']; + $result['postdate'] = $output['postdate']; + $result['auth'] = $output['auth']; + $result['avr'] = $output['avr']; //TRANSACTION avr + $result['ammount'] = $output['amt']; //Transaction Amount + $result['udf1'] = $output['udf1']; //UDF1 + $result['udf2'] = $output['udf2']; //UDF2 + $result['udf3'] = $output['udf3']; //UDF3 + $result['udf4'] = $output['udf4']; //UDF4 + $result['udf5'] = $output['udf5']; + //Decryption logice starts + $result['data']=$decrytedData; + $result['ErrorText']= $ResErrorText; //Error + $result['Error'] = $ResErrorNo; + }else{ + $result['status'] = 'error'; + $result['paymentid'] = $ResPaymentId; + $result['trackid'] = $ResTrackID; + $result['tranid'] = $ResTranId; + $result['ref'] = $ResRef; + $result['result'] = 'error'; + $result['data']= sanitize_text_field(http_build_query($_REQUEST)); + $result['postdate'] = $ResPosdate; + $result['auth'] = $ResAuth; + $result['avr'] = $ResAVR; //TRANSACTION avr + $result['ammount'] = $ResAmount; //Transaction Amount + $result['udf1'] = $Resudf1; //UDF1 + $result['udf2'] = $Resudf2; //UDF2 + $result['udf3'] = $Resudf3; //UDF3 + $result['udf4'] = $Resudf4; //UDF4 + $result['udf5'] = $Resudf5; + $result['ErrorText']= $ResErrorText; //Error + $result['Error'] = $ResErrorNo; + } + + } + else + { + // error + $result['status'] = 'error'; + $result['paymentid'] = $ResPaymentId; + $result['trackid'] = $ResTrackID; + $result['tranid'] = $ResTranId; + $result['ref'] = $ResRef; + $result['result'] = 'error'; + $result['data']= sanitize_text_field(http_build_query($_REQUEST)); + $result['ErrorText']= $ResErrorText; //Error + $result['Error'] = $ResErrorNo; //Error Number + $result['postdate'] = $ResPosdate ; //Postdate + $result['auth'] = $ResAuth; //Auth Code + $result['avr'] = $ResAVR; //TRANSACTION avr + $result['ammount'] = $ResAmount; //Transaction Amount + $result['udf1'] = $Resudf1; //UDF1 + $result['udf2'] = $Resudf2; //UDF2 + $result['udf3'] = $Resudf3; //UDF3 + $result['udf4'] = $Resudf4; //UDF4 + $result['udf5'] = $Resudf5; + } + + //UDF5 + return $result; + } + /** ======== Payment Encrypt Functions Started ====== + * this functions created by knet devolper don't change any thing + */ + public function encryptAES($str,$key) + { + $str = $this->pkcs5_pad($str); + $encrypted = openssl_encrypt($str, 'AES-128-CBC', $key, OPENSSL_ZERO_PADDING, $key); + $encrypted = base64_decode($encrypted); + $encrypted=unpack('C*', ($encrypted)); + $encrypted=$this->byteArray2Hex($encrypted); + $encrypted = urlencode($encrypted); + return $encrypted; + } + + public function pkcs5_pad ($text) + { + $blocksize = 16; + $pad = $blocksize - (strlen($text) % $blocksize); + return $text . str_repeat(chr($pad), $pad); + } + public function byteArray2Hex($byteArray) + { + $chars = array_map("chr", $byteArray); + $bin = join($chars); + return bin2hex($bin); + } + + public function decrypt($code,$key) + { + $code = $this->hex2ByteArray(trim($code)); + $code=$this->byteArray2String($code); + $iv = $key; + $code = base64_encode($code); + $decrypted = openssl_decrypt($code, 'AES-128-CBC', $key, OPENSSL_ZERO_PADDING, $iv); + return $this->pkcs5_unpad($decrypted); + } + + public function hex2ByteArray($hexString) + { + $string = hex2bin($hexString); + return unpack('C*', $string); + } + + + public function byteArray2String($byteArray) + { + $chars = array_map("chr", $byteArray); + return join($chars); + } + + public function pkcs5_unpad($text) + { + $pad = ord($text{strlen($text)-1}); + if ($pad > strlen($text)) { + return false; + } + if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) { + return false; + } + return substr($text, 0, -1 * $pad); + } + /** ======== Payment Encrypt Functions Ended ====== */ + + } + + } + + /** + * Add the Gateway to WooCommerce + **/ + function woocommerce_add_wc_knet_gateway($methods) { + global $WC_KNET_CLASS_NAME; + $methods[] = $WC_KNET_CLASS_NAME; + return $methods; + } + add_filter('woocommerce_payment_gateways', 'woocommerce_add_wc_knet_gateway' ); + + /** + * load plugin language + * @param $mofile + * @param $domain + * @return string + */ + function wc_knet_load_textdomain() { + load_plugin_textdomain( 'wc_knet', false, basename( dirname( __FILE__ ) ) . '/languages/' ); + } + add_action( 'plugins_loaded', 'wc_knet_load_textdomain' ); + + + /** + * add knet responce query var + */ + add_filter( 'query_vars', function( $query_vars ) { + $query_vars[] = 'knetresponce'; + $query_vars[] = 'wc_knet_export'; + return $query_vars; + } ); + /** + * define knet responce + */ + add_action("wp",function($request) + { + + if( isset($request->query_vars['knetresponce']) && null !== sanitize_text_field($request->query_vars['knetresponce']) && sanitize_text_field($request->query_vars['knetresponce']) == "success") + { + $WC_Gateway_Knet = new WC_Gateway_Knet(); + $url = $WC_Gateway_Knet->updateOrder(); + + if ( wp_redirect( $url ) ) + { + exit; + } + } + + }); + + add_action("admin_init",function ($request){ + $action = esc_attr($_GET["wc_knet_export"] ?? ""); + if(is_admin()){ + if(sanitize_text_field($action) == "excel"){ + $rows = wc_knet_trans_grid::get_transations(1000); + $list[] =[__('Order', "wc_knet"), __('Status', "wc_knet"), __('Result', "wc_knet"), __('Amount', "wc_knet"), __('Payment id', "wc_knet"), __('Tracking id', "wc_knet"), __('Transaction id', "wc_knet"), __('Refrance id', "wc_knet"), __('Created at', "wc_knet") ]; + if($rows){ + foreach ($rows as $row){ + $list[] = [$row['order_id'],__($row['status'],"wc_kent"),$row['result'],$row['amount'],$row['payment_id'],$row['track_id'],$row['tran_id'],$row['ref_id'],$row['created_at']]; + } + } + $xlsx = SimpleXLSXGen::fromArray( $list ); + $xlsx->downloadAs(date("YmdHis").'.xlsx'); // or downloadAs('books.xlsx') or $xlsx_content = (string) $xlsx + exit(); + }elseif (sanitize_text_field($action) == "csv"){ + + $rows = wc_knet_trans_grid::get_transations(1000); + if($rows){ + $filename = date('YmdHis') . ".csv"; + $f = fopen('php://memory', 'w'); + $delimiter = ","; + $head = [__('Order', "wc_knet"), __('Status', "wc_knet"), __('Result', "wc_knet"), __('Amount', "wc_knet"), __('Payment id', "wc_knet"), __('Tracking id', "wc_knet"), __('Transaction id', "wc_knet"), __('Refrance id', "wc_knet"), __('Created at', "wc_knet") ]; + fputcsv($f, $head,$delimiter); + foreach ($rows as $row){ + $listData = [$row['order_id'],__($row['status'],"wc_kent"),$row['result'],$row['amount'],$row['payment_id'],$row['track_id'],$row['tran_id'],$row['ref_id'],$row['created_at']]; + fputcsv($f, $listData, $delimiter); + } + fseek($f, 0); + header('Content-Type: text/csv'); + header('Content-Disposition: attachment; filename="' . $filename . '";'); + fpassthru($f); + exit(); + } + } + } + + }); + // call to install data + register_activation_hook( __FILE__, 'create_transactions_db_table' ); + + /** + * notify is currency not KWD + */ + add_action('admin_notices', 'wc_knet_is_curnancy_not_kwd'); + function wc_knet_is_curnancy_not_kwd(){ + $currency = get_option('woocommerce_currency'); + if(isset($currency) && $currency != "KWD"){ + echo '
+

'.__("currency must be KWD when using this knet payment","wc_knet").'

+
'; + } + } + +?> \ No newline at end of file diff --git a/languages/wc_knet-ar.mo b/languages/wc_knet-ar.mo new file mode 100644 index 0000000..b475f79 --- /dev/null +++ b/languages/wc_knet-ar.mo @@ -0,0 +1,112 @@ +#: index.php:67 +msgid "Knet" +msgstr "كي نت" + +#: index.php:99 +msgid "Tranportal Id" +msgstr "Tranportal Id" + +#: index.php:101 +msgid "Necessary data requested from the bank " +msgstr "بيانات ضرورية تطلب من البنك" + +#: index.php:105 +msgid "Transportal Password" +msgstr "" + +#: index.php:107 +msgid "Necessary data requested from the bank " +msgstr "بيانات ضرورية تطلب من البنك" + +#: index.php:112 +msgid "Terminal Resource Key" +msgstr "" + +#: index.php:114 +msgid "Necessary data requested from the bank " +msgstr "بيانات ضرورية تطلب من البنك" + +#: index.php:126 +msgid "Knet" +msgstr "" + +#: index.php:167 +msgid "Knet can't get data" +msgstr "" + +#: index.php:281 +msgid "Order not found" +msgstr "لم يتم العثور علي الطب" + +#: index.php:82 +msgid "Thank you. Your order has been" +msgstr "شكرا لك حالة طلبك هي :" + +#: index.php:90 +msgid "Order" +msgstr "طلبك " + +#: index.php:629 +msgid "currency must be KWD when using this knet payment" +msgstr "العملة يجب ان تكون بالدينار الكويت KWD عندما تحدد الكي نت كخيار للدفع " + + +msgid "fail" +msgstr "فشلت" + +msgid "success" +msgstr "نجحت" + +msgid "Knet transactions" +msgstr "عمليت الكي نت" + +msgid "Filter" +msgstr "تصفية" + +msgid "WC_Knet_List" +msgstr "" + +msgid "Order" +msgstr "رقم الطلب" + +msgid "Status" +msgstr "الحالة" + +msgid "Result" +msgstr "نتيجة الدفع" + +msgid "Amount" +msgstr "المبلغ" + +msgid "Payment id" +msgstr "" + +msgid "Tracking id" +msgstr "" + +msgid "Transaction id" +msgstr "" + +msgid "Refrance id" +msgstr "" + +msgid "Created at" +msgstr "تمت بتاريخ" + +msgid "Reset" +msgstr "اعادة تهيئة" + +msgid "Export excel" +msgstr "تصدير الي اكسل" + +msgid "Export csv" +msgstr "تصدير ال csv" + +msgid "No Transations avaliable." +msgstr "لم يتم العثور علي عمليات دفع" + + + + + + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..eecf835 --- /dev/null +++ b/readme.md @@ -0,0 +1,58 @@ +=== Payment Gateway for knet on WooCommerce === +Contributors: hassan +Tags: K-Net, knet, knetv2, payment, kuwait, woocommerce, ecommerce, payment, gateway +Requires at least: 5.6 +Tested up to: 5.8 +Tested in WooCommerce : 5.8 +Requires PHP: 7.0 +Stable tag: 2.0.0 +License: MIT +License URI: https://choosealicense.com/licenses/mit/ + +نساعدك في تطوير اعمالك الخاصه بتقديم الاضافة الجديد +الخاصة بالدفع عن طريق بوابة الكي نت بعد تحديثها +وسع دائرة عملائك باتاحة امكانية الدفع عن طريق الكي نت +========== +We help you to develop your business by introducing the new add-on +For payment through the K-Net portal, after it has been updated +Expand your customers' circle by making the payment available via Knet + +## Installation + +download and unzip to plugins folder +
+or +From merchant’s WordPress admin +1. Go to plugin section-> Add new +2. Search for “Payment Gateway for knet on WooCommerce” +3. Click on Install Now +4. Click on Activate + + +## Usage + +go to woocommerce setting in side menu and select tab payment and active knet v2 from list + + +## Changelog + +== Changelog == + += 2.0.0 = +* add knet transations list for order +* export transations to csv,excel +* change licenses to mit + += 1.1.0 = +* add order status in received page. +* colored status +- ![#0470fb](https://via.placeholder.com/15/0470fb/000000?text=+) `pending` +- ![#fbbd04](https://via.placeholder.com/15/fbbd04/000000?text=+) `processing` +- ![#04c1fb](https://via.placeholder.com/15/0470fb/000000?text=+) `on-hold` +- ![#green](https://via.placeholder.com/15/green/000000?text=+) `completed` +- ![#fb0404](https://via.placeholder.com/15/fb0404/000000?text=+) `cancelled,refunded,failed` + + +## License + +[MIT](https://choosealicense.com/licenses/mit/) \ No newline at end of file diff --git a/transactions.php b/transactions.php new file mode 100644 index 0000000..f39c859 --- /dev/null +++ b/transactions.php @@ -0,0 +1,62 @@ +get_charset_collate(); + $table_name = $wpdb->prefix.WC_KNET_TABLE; + $sql = "CREATE TABLE IF NOT EXISTS $table_name ( + id int(11) NOT NULL AUTO_INCREMENT, + order_id int(11) NOT NULL, + payment_id varchar(255) NOT NULL, + track_id varchar(255) NOT NULL, + amount DECIMAL(20,3) DEFAULT 0.000 NOT NULL, + tran_id varchar(255) NULL, + ref_id varchar(255) NULL, + status varchar(255) DEFAULT '".STATUS_FAIL."' NOT NULL, + result varchar(255) DEFAULT '".STATUS_NEW."' NOT NULL, + info text NULL, + created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + PRIMARY KEY (id), + INDEX (id, order_id, payment_id, result) + ) $charset_collate;"; + + require_once( ABSPATH . 'wp-admin/includes/upgrade.php' ); + dbDelta( $sql ); + add_option( 'wc_knet_db_version', WC_KNET_DV_VERSION); +} + +/** + * create new transation record + * @param $data + * @return bool|false|int + */ +add_action("wc_knet_create_new_transation","fun_wc_knet_create_new_transation", 10, 2); +function fun_wc_knet_create_new_transation($order,$transation_data){ + global $wpdb; + $table_name = $wpdb->prefix.WC_KNET_TABLE; + try { + return $wpdb->insert( + $table_name, + [ + 'order_id' => $order->id, + 'payment_id' => $transation_data["payment_id"], + 'track_id' => $transation_data["track_id"], + 'tran_id' => $transation_data["tran_id"], + 'ref_id' => $transation_data["ref_id"], + 'status' => $transation_data["status"], + 'result' => $transation_data["result"], + 'amount'=>$transation_data["amount"], + 'info' => json_encode($transation_data), + 'created_at' => date("Y-m-d H:i:s"), + ] + ); + }catch (Exception $e){ + return false; + } +} + diff --git a/wc_knet_trans_grid.php b/wc_knet_trans_grid.php new file mode 100644 index 0000000..46acfd8 --- /dev/null +++ b/wc_knet_trans_grid.php @@ -0,0 +1,350 @@ + __( 'WC_Knet_List', "wc_knet" ), //singular name of the listed records + 'plural' => __( 'WC_Knet_List', "wc_knet" ), //plural name of the listed records + 'ajax' => false //should this table support ajax? + + ]); + $this->db = $wpdb; + $this->table = $this->db->prefix.WC_KNET_TABLE; + } + + + private function filter_query(){ + $query = ""; + if(isset($_REQUEST["order_id"]) && !empty($_REQUEST["order_id"])){ + $order_id = sanitize_text_field($_REQUEST["order_id"]); + $query .=" AND `order_id` = $order_id"; + } + if(isset($_REQUEST["status"]) && !empty($_REQUEST["status"])){ + $status = sanitize_text_field($_REQUEST["status"]); + $query .=" AND `status` = '$status'"; + } + if(isset($_REQUEST["result"]) && !empty($_REQUEST["result"])){ + $result = sanitize_text_field($_REQUEST["result"]); + $query .=" AND `result` LIKE '%$result%'"; + } + if(isset($_REQUEST["amount"]) && !empty($_REQUEST["amount"])){ + $amount = sanitize_text_field($_REQUEST["amount"]); + $query .=" AND `amount` = $amount"; + } + if(isset($_REQUEST["payment_id"]) && !empty($_REQUEST["payment_id"])){ + $payment_id = sanitize_text_field($_REQUEST["payment_id"]); + $query .=" AND `payment_id` = $payment_id"; + } + if(isset($_REQUEST["track_id"]) && !empty($_REQUEST["track_id"])){ + $track_id = sanitize_text_field($_REQUEST["track_id"]); + $query .=" AND `track_id` = $track_id"; + } + if(isset($_REQUEST["tran_id"]) && !empty($_REQUEST["tran_id"])){ + $tran_id = sanitize_text_field($_REQUEST["tran_id"]); + $query .=" AND `tran_id` = $tran_id"; + } + if(isset($_REQUEST["ref_id"]) && !empty($_REQUEST["ref_id"])){ + $ref_id = sanitize_text_field($_REQUEST["ref_id"]); + $query .=" AND `ref_id` = $ref_id"; + } + if(isset($_REQUEST["created_at"]) && !empty($_REQUEST["created_at"])){ + $created_at = sanitize_text_field($_REQUEST["created_at"]); + $query .=" AND `created_at` LIKE '%$created_at%'"; + } + + return $query; + } + + public function search_box( $text, $input_id ) { + if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) { + // return; + } + + $input_id = $input_id . '-search-input'; + + + ?> +
+ + '; + } + if ( ! empty( $_REQUEST['order'] ) ) { + echo ''; + } + if ( ! empty( $_REQUEST['post_mime_type'] ) ) { + echo ''; + } + if ( ! empty( $_REQUEST['detached'] ) ) { + echo ''; + } + + ?> +
+ + " placeholder="" /> +
+ +
+ + +
+
+ + " placeholder="" /> +
+
+ + " placeholder="" /> +
+
+ + " placeholder="" /> +
+
+ + " placeholder="" /> +
+
+ + " placeholder="" /> +
+
+ + " placeholder="" /> +
+
+ + " placeholder="" /> +
+
+ +
+

+ + __('Order', "wc_knet"), + 'status'=>__('Status', "wc_knet"), + 'result'=>__('Result', "wc_knet"), + 'amount'=>__('Amount', "wc_knet"), + 'payment_id'=>__('Payment id', "wc_knet"), + 'track_id'=>__('Tracking id', "wc_knet"), + 'tran_id'=>__('Transaction id', "wc_knet"), + 'ref_id'=>__('Refrance id', "wc_knet"), + 'created_at'=>__('Created at', "wc_knet"), + ); + } + + public function get_sortable_columns() { + return $sortable = array( + 'order_id'=>'order_id', + 'status'=>'status', + 'result'=>'result', + 'payment_id'=>'payment_id', + 'track_id'=>'track_id', + 'amount'=>'amount', + 'tran_id'=>'tran_id', + 'ref_id'=>'ref_id', + 'created_at'=>'created_at', + ); + } + /** + * Render a column when no column specific method exists. + * + * @param array $item + * @param string $column_name + * + * @return mixed + */ + public function column_default( $item, $column_name ) { + switch ( $column_name ) { + case 'order_id': + return sprintf("#%s",get_edit_post_link($item[$column_name]),$item[$column_name]); + case 'status': + return ($item[$column_name] == "fail") ? sprintf("%s",__($item[ $column_name ],"wc_knet")) : sprintf("%s",__($item[ $column_name ],"wc_knet")); + case 'result': + return ($item[$column_name] != "CAPTURED") ? sprintf("%s",$item[ $column_name ]) : sprintf("%s",$item[ $column_name ]); + default: + return $item["$column_name"]; //Show the whole array for troubleshooting purposes + } + } + /** + * Handles data query and filter, sorting, and pagination. + */ + public function prepare_items() { + + $this->_column_headers = $this->get_column_info(); + + /** Process bulk action */ + $this->process_bulk_action(); + + $per_page = $this->get_items_per_page( 'trans_per_page', 5 ); + $current_page = $this->get_pagenum(); + $total_items = self::record_count(); + + $this->set_pagination_args( [ + 'total_items' => $total_items, //WE have to calculate the total number of items + 'per_page' => $per_page //WE have to determine how many items to show on a page + ] ); + + $this->items = self::get_transations( $per_page = 5, $page_number = 1 ); + } + /** + * Returns the count of records in the database. + * + * @return null|string + */ + public static function record_count() { + $sql = "SELECT COUNT(*) FROM ".(new self)->table." WHERE 1=1 ".(new self)->filter_query(); + return (new self)->db->get_var( $sql ); + } + public static function get_transations($per_page, $page_number = 1) + { + + $sql = "SELECT * FROM ".(new self)->table." WHERE 1=1 ".(new self)->filter_query(); + + if ( ! empty( $_REQUEST['orderby'] ) ) { + $sql .= ' ORDER BY ' . esc_sql( $_REQUEST['orderby'] ); + $sql .= ! empty( $_REQUEST['order'] ) ? ' ' . esc_sql( $_REQUEST['order'] ) : ' ASC'; + } + + $sql .= " LIMIT $per_page"; + + $sql .= ' OFFSET ' . ( $page_number - 1 ) * $per_page; + + $result = (new self)->db->get_results( $sql, 'ARRAY_A' ); + + return $result; + } +} + +class WC_KNET_Plugin +{ + +// class instance + static $instance; + +// customer WP_List_Table object + public $transations_obj; + +// class constructor + public function __construct() + { + add_filter('set-screen-option', [__CLASS__, 'set_screen'], 10, 3); + add_action('admin_menu', [$this, 'plugin_menu']); + + } + public static function set_screen( $status, $option, $value ) { + return $value; + } + /** Singleton instance */ + public static function get_instance() { + if ( ! isset( self::$instance ) ) { + self::$instance = new self(); + } + + return self::$instance; + } + /** + * Screen options + */ + public function screen_option() { + + $option = 'per_page'; + $args = [ + 'label' => 'Transations count', + 'default' => 5, + 'option' => 'trans_per_page' + ]; + + add_screen_option( $option, $args ); + + $this->transations_obj = new wc_knet_trans_grid(); + } + public function plugin_menu() { + + $hook =add_submenu_page( + 'woocommerce', + __( 'Knet transactions', 'wc_knet' ), + __( 'Knet transactions', 'wc_knet' ), + 'manage_woocommerce', + "wc-knet-transactions", + [ $this, 'plugin_settings_page' ], + 6 + ); + add_action( "load-$hook", [ $this, 'screen_option' ] ); + + + } + /** + * Plugin settings page + */ + public function plugin_settings_page() { + ?> +
+

+ +
+ +
+
+
+
+ transations_obj->prepare_items(); + $this->transations_obj->display(); ?> +
+
+
+
+
+ transations_obj->search_box(_e( 'Filter', 'wc_knet' ), "wc_knet_filter") ?> +
+
+
+