base_path_segments = explode('/', $base_path); array_filter($this->base_path_segments, function ($value) { return $value !== ""; }); if (!empty($this->base_path_segments)) { $this->record_patch_cache_key = implode('_', $this->base_path_segments); $base_path = array_pop($this->base_path_segments); } $this->base_path = $base_path; } /** * Get record * * @param int $index * * @return string */ public function getRecord($index = 0) { $record = parent::getRecord($index); if ($index === $this->getRecordCount() - 1 && true === $this->last_record_incomplete) { $record = $this->fixRecord($record); } return $this->wrapWithRecordTag($record); } /** * Fix a broken xml segment * * @param $record * * @return bool|string */ public function fixRecord($record) { $open = "/<[^\/][^>]+[^\/]>/"; $close = "/<\/[^>]+[^\/]>/"; $depth = 0; $started = false; $open_nodes = array(); $chunk = $record; while (preg_match( "/<[^?|!\-\-][^>]*>/", $chunk, $matches, PREG_OFFSET_CAPTURE ) !== 0) { if (isset($matches[0]) && isset($matches[0][0])) { list($captured, $offset) = $matches[0]; if (preg_match($open, $captured) === 1) { $started = true; $depth++; $node_name = $this->getNodeName($captured); $open_nodes[] = $node_name; } else if (preg_match($close, $captured) === 1) { $depth--; array_pop($open_nodes); } $string_offset = $offset + strlen($captured); $chunk = substr($chunk, $string_offset); } if ($started && 0 === $depth) { // We should be at the end of the xml return substr($record, 0, strlen($chunk)); } } if (!empty($open_nodes)) { // Add closing tags for open nodes foreach ($open_nodes as $node) { $record .= ""; } } return $record; } /** * Get node name from xml tag * * @param $node * * @return bool|string */ public function getNodeName($node) { preg_match("/<\/?([a-zA-Z0-9\-_\.:]+)[ ]*[^>]*>/", $node, $matches); if (isset($matches[0]) && isset($matches[1])) { $name = $matches[1]; } else { $name = substr($node, 1, strlen($node) - 2); } return $name; } /** * Generate file position index. * * Read through entire xml file generating record start and end positions * * @return mixed */ public function generateIndex() { $this->record_counter = 0; $this->record_start_index = 0; $last_percent = 0; $size = intval($this->get_file_size($this->getFileHandle())); $this->config->set('process', 0); $this->namespace_list = []; rewind($this->getFileHandle()); while (!feof($this->getFileHandle())) { $this->chunk .= $this->getChunk(); $this->chunk = $this->read_chunk_xml_nodes($this->chunk); // only read the first 1mb of file if ($this->is_processing && ($this->record_counter > 0 || ftell($this->getFileHandle()) > $this->process_max_size)) { break; } $current_pos = intval(ftell($this->getFileHandle())); if ($size > 0 && $current_pos > 0) { $percent = round($current_pos / $size, 2); if ($percent > $last_percent) { do_action('iwp/importer/process', $last_percent * 100); $this->config->set('process', $last_percent * 100); $last_percent = $percent; if (!is_null(iwp()->importer)) { $state = ImporterState::get_state(iwp()->importer->getId()); if (isset($state['status']) && $state['status'] === 'cancelled') { return; } } } } } $this->config->set('namespace_list', $this->namespace_list); // if file is incomplete do we try and fix the record, or skip it? if (!$this->is_processing && true === $this->record_opened) { $this->record_opened = false; $this->last_record_incomplete = true; $this->setIndex( $this->record_counter, $this->record_start_index, $this->chunk_offset ); } } /** * Read chunk from file * * @return bool|string */ public function getChunk() { return fread($this->getFileHandle(), $this->chunk_size); } /** * Read all xml nodes in chunk */ public function readChunkXmlNodes() { $tags = [ ["", true, 0], ["", true, 0], ["", true, 0], ["", true, 0], ["", false, -1], ["<", "/>", false, 0], ["<", ">", false, 1], ]; while (preg_match("/<[^>]+>/", $this->chunk, $matches, PREG_OFFSET_CAPTURE) !== 0) { list($captured, $offset) = $matches[0]; $this->record_offset = $offset; foreach ($tags as $tag) { list($open_tag, $close_tag, $search_for_close, $depth_delta) = $tag; if (strpos($captured, $open_tag) === 0) { if (strlen($captured) - strlen($close_tag) === strpos($captured, $close_tag)) { // Tag Found if ($search_for_close === false) { $this->record_node($captured, $depth_delta); } break; } elseif ($search_for_close === true) { $close_tag_position = strpos($this->chunk, $close_tag, $offset); if ($close_tag_position !== false) { // Close tag is found in current chunk $captured = substr($this->chunk, $offset, $close_tag_position + strlen($close_tag) - $offset); if ($search_for_close === false) { $this->record_node($captured, $depth_delta); } } else { // We need to load next chunk to find return; } break; } } } $string_offset = $offset + strlen($captured); $this->chunk_offset += $string_offset; $this->chunk = substr($this->chunk, $string_offset); } } /** * Optimised version of readChunkXMLNodes * * @param string $chunk * @return string */ public function read_chunk_xml_nodes($chunk) { $tags = [ " ["", true, 0], "", true, 0], " ["", true, 0], " ["", true, 0], " ["", false, -1], "<" => ["<", "/>", false, 0], "<" => ["<", ">", false, 1], ]; // 1.Loop through the file, each node at a time $chunk_offset = 0; while (preg_match("/<[^>]+>/", $chunk, $matches, PREG_OFFSET_CAPTURE, $chunk_offset) !== 0) { list($captured, $offset) = $matches[0]; // length of content between last and current tag $content_length = ($offset - $chunk_offset); $chunk_offset += $content_length + strlen($captured); if (preg_match('/^(<\?|