id = $id; $this->data = $data; $this->storage = Storage::disk('local'); $this->path = ''; $this->url = env('WEBHOOK_CORE_URL') . '/webhooks'; $this->secret = env('WEBHOOK_CORE_SECRET'); } /** * Execute the job. * * @return void */ public function handle() { try { $this->setupData(); $this->createDocx(); $this->convertToOriginalDocumentFormat(); $this->sendResponse('success'); } catch (\Exception $exception) { \Illuminate\Support\Facades\Log::info('RecreateDocument@handle: ' . $exception->getMessage()); \Illuminate\Support\Facades\Log::info($exception->getTraceAsString()); $this->sendResponse('fail'); } } protected function setupData() { $text = $this->data['contents']['text']; $textMapper = []; foreach ($this->data['contents']['elements'] as $element) { $textMapper[$element['hash']] =substr( $text, $element['range_start'], $element['range_end'] - $element['range_start'] + 1 ); } $this->data['elements'] = $this->updateText($this->data['elements'], $textMapper)['elements']; } protected function updateText($elements, $textMapper, $parentElement = null) { $stoppedAtIndex = null; $collectionToAppend = []; for ($index = 0; $index < count($elements); $index++) { $element = $elements[$index]; if (array_key_exists('hash', $element)) { $result = $this->processText($element, $textMapper); if ( ! $result['is_collection']) { $elements[$index] = $result['element']; } else { if ($parentElement && $parentElement['element_type'] === 'TextRun') { $stoppedAtIndex = $index; $collectionToAppend = $result['elements']; break; } else { // We have a collection of elements instead of one, so we must push its siblings to make room. $numberOfPlacesToMove = count($result['elements']) - 1; // Move siblings to make room for collection. for ($i = count($elements) - 1; $i > $index; $i--) { $elements[$i + $numberOfPlacesToMove] = $elements[$i]; } foreach ($result['elements'] as $i => $collectionElement) { $elements[$index + $i] = $collectionElement; } $index += $numberOfPlacesToMove; } } } if ( array_key_exists('text_object', $element) && array_key_exists('text', $element['text_object']) ) { // $elements[$index]['text_object']['text'] = $textMapper[$element['text_object']['hash']]; $result = $this->processText($element['text_object'], $textMapper); if ( ! $result['is_collection']) { $elements[$index]['text_object'] = $result['element']; } else { // if ($parentElement && $parentElement['element_type'] === 'TextRun') { // $stoppedAtIndex = $index; // $collectionToAppend = $result['elements']; // // break; // } else { // // We have a collection of elements instead of one, so we must push its siblings to make room. // $numberOfPlacesToMove = count($result['elements']) - 1; // // // Move siblings to make room for collection. // for ($i = count($elements) - 1; $i > $index; $i--) { // $elements[$i + $numberOfPlacesToMove] = $elements[$i]; // } // // foreach ($result['elements'] as $i => $element) { // $elements[$index + $i] = $element; // } // // $index += $numberOfPlacesToMove; // } } } if (isset($elements[$index]['elements'])) { $result = $this->updateText($elements[$index]['elements'], $textMapper, $element); $elements[$index]['elements'] = $result['elements']; if ($result['has_stopped']) { // One of the child has become a 'TextRun' and the current $element is also a 'TextRun' // so the child will become a sibling of the $element. // Start from the end of the array and move elements. $numberOfPlacesToMove = count($result['collection_to_append']) + count($result['unprocessed_elements']); for ($i = count($elements) - 1; $i > $index; $i--) { $elements[$numberOfPlacesToMove + $i] = $elements[$i]; } foreach ($result['collection_to_append'] as $i => $collectionElement) { $elements[$index + 1 + $i] = $collectionElement; } // 4 5 6 $elements[$index + 1] = $result['text_run_element']; foreach ($result['unprocessed_elements'] as $i => $unprocessedElement) { $elements[$index + count($result['collection_to_append']) + $i] = $unprocessedElement; } // Skip 'text_run_element' processing. $index += 1; } } } if ($stoppedAtIndex === null) { return [ 'has_stopped' => false, 'elements' => $elements, ]; } $remainingElements = array_splice($elements, 0, $stoppedAtIndex); $unprocessedElements = array_splice($elements, 1); return [ 'has_stopped' => true, 'elements' => $remainingElements, 'collection_to_append' => $collectionToAppend, 'unprocessed_elements' => $unprocessedElements, ]; } protected function processText($element, $textMapper) { $text = $textMapper[$element['hash']]; if ($element['text'] === $text) { return [ 'is_collection' => false, 'element' => $element, ]; } $textWithDisplacement = $text; preg_match_all('/{[^\/][^{}]*}/', $textWithDisplacement, $accoladeMatches); // Nothing found. if (count($accoladeMatches[0]) === 0) { $element['text'] = $textWithDisplacement; return [ 'is_collection' => false, 'element' => $element, ]; } // In order to add bookmarks we can create a list of parent element and bookmark element which will be appended // at the end of the elements list, so it will be created in the net steps. $elements = []; $textContents = $textWithDisplacement; $accoladeMatches = array_unique($accoladeMatches[0]); foreach ($accoladeMatches as $accoladeMatch) { $textMatch = substr($accoladeMatch, 1, strlen($accoladeMatch) - 2); $pattern = '/(' . $accoladeMatch . ')([^{}\/]+)({\/' . $textMatch . '})/'; preg_match_all($pattern, $textWithDisplacement, $matches); if (($matchesCount = count($matches[0])) > 0) { for ($i = 0; $i < $matchesCount; $i++) { $foundText = $matches[0][$i]; $displacement = $matches[1][$i]; $textWithoutDisplacement = trim($matches[2][$i]); $index = strpos($textContents, $foundText); $plainText = substr($textContents, 0, $index); if ($plainText) { $elements[] = [ 'element_type' => 'Text', 'text' => $plainText, 'font_style' => $element['font_style'], 'paragraph_style' => $element['paragraph_style'], ]; } $elements[] = [ 'element_type' => 'TextRun', 'paragraph_style' => $element['paragraph_style'], 'elements' => [ [ 'element_type' => 'Text', 'text' => $textWithoutDisplacement, 'font_style' => $element['font_style'], 'paragraph_style' => $element['paragraph_style'], ], [ 'element_type' => 'Bookmark', 'name' => substr($displacement, 1, strlen($displacement) - 2), ] ], ]; $textContents = substr($textContents, $index + strlen($foundText)); } } } if ($textContents) { $elements[] = [ 'element_type' => 'Text', 'text' => $textContents, 'font_style' => $element['font_style'], 'paragraph_style' => $element['paragraph_style'], ]; } return [ 'is_collection' => true, 'elements' => $elements, ]; } protected function createDocx() { $path = 'contracts/' . $this->id . '-document.docx'; $writer = new DocxWriter($this->storage, $path); $writer->execute($this->data); $this->path = $path; } /** * @throws \Exception */ protected function convertToOriginalDocumentFormat() { if ($this->data['document_format'] === 'docx') { return; } $convertor = new DocxConvertor($this->storage, $this->path); if ($this->data['document_format'] === 'pdf') { $convertor->convertToPdfWithLibreOffice(); } if ($this->data['document_format'] === 'odt') { $convertor->convertToODT(); } if ($this->data['document_format'] === 'rtf') { $convertor->convertToRTF(); } if ($this->data['document_format'] === 'doc') { $convertor->convertToDOC(); } if ($this->data['document_format'] === 'txt') { $convertor->convertToTXT(); } $this->path = $convertor->getPath(); } protected function sendResponse($status) { try { WebhookCall::create() ->url($this->url) ->payload(['data' => [ 'id' => $this->id, 'content' => '', 'file_result_type' => 'document-recreated', 'document_format' => $this->data['document_format'], 'status' => $status, ]]) ->useSecret($this->secret) ->dispatch(); return true; } catch (\Exception $exception) { Log::error('RecreateDocument@sendDocument: ' . $exception->getMessage()); return false; } } }