diff --git a/.upgrade.yml b/.upgrade.yml index 189dffc..f8b12ee 100644 --- a/.upgrade.yml +++ b/.upgrade.yml @@ -1,6 +1,6 @@ mappings: - DocumentConverterDecorator: SilverStripe\DocumentConverter\ConvertsDocumentsExtension - DocumentImportField: SilverStripe\DocumentConverter\DocumentConversionField - DocumentImportIFrameField_Importer: SilverStripe\DocumentConverter\DocumentConverter - DocumentImportInnerField: SilverStripe\DocumentConverter\DocumentImporterField + DocumentConverterDecorator: SilverStripe\DocumentConverter\PageExtension + DocumentImportField: SilverStripe\DocumentConverter\SettingsField + DocumentImportIFrameField_Importer: SilverStripe\DocumentConverter\ServiceConnector + DocumentImportInnerField: SilverStripe\DocumentConverter\ImportField DocumentConverterTest: SilverStripe\DocumentConverter\Tests\DocumentConverterTest diff --git a/composer.json b/composer.json index 8a43c26..530e9ad 100644 --- a/composer.json +++ b/composer.json @@ -5,9 +5,12 @@ "license": "BSD-3-Clause", "keywords": ["silverstripe", "cwp"], "require": { - "silverstripe/cms": "^4" + "silverstripe/cms": "^4", + "silverstripe/asset-admin": "^1" }, "require-dev": { + "ext-tidy": "*", + "ext-zip": "*", "phpunit/phpunit": "^5.7", "squizlabs/php_codesniffer": "^3.0" }, diff --git a/lang/ar.yml b/lang/ar.yml index 992c2b3..503092a 100644 --- a/lang/ar.yml +++ b/lang/ar.yml @@ -1,10 +1,10 @@ ar: - SilverStripe\DocumentConverter\DocumentConverter: + SilverStripe\DocumentConverter\ServiceConnector: PROCESSFAILED: 'لم يتم التمكن من معالجة الوثيقة, من فضلك قم بمراجعة أنك قمت برفع الملف بصيغة .دى أو سى أو صيغة .دى أو سى إكس.' SERVERUNREACHABLE: 'لم يتم التواصل مع سيرفر تحويل الوثائق. من فضلك حاول مرة أخرى لاحقا أو اتصل بمشرف النظام الخاص بك.' - SilverStripe\DocumentConverter\ConvertsDocumentsExtension: + SilverStripe\DocumentConverter\PageExtension: ImportTab: استيراد - SilverStripe\DocumentConverter\DocumentConversionField: + SilverStripe\DocumentConverter\SettingsField: ChooseFolder: 'اختر مجلد لحفظ هذا الملف' EachH1: 'لكل عنوان واحد' EachH2: 'لكل عنوان اثنين' @@ -14,7 +14,7 @@ ar: KeepSource: 'احتفظ بالملف الأصلي. وقم بإضافة رابط له في جدول المحتويات، إذا أمكن ذلك.' SplitHeader: 'تقسيم المستند إلى صفحات' publishPages: 'نشر الصفحات المُعدلة (لا يُوصى به إلا إذا كنت متأكد من ناتج التحويل)' - SilverStripe\DocumentConverter\DocumentImporterField: + SilverStripe\DocumentConverter\ImportField: ATTACHONCESAVED2: 'يمكن إرفاق الملفات عند حفظ السجل للمرة الأولى.' UploadField: ATTACHFILE: 'إرفاق ملف' diff --git a/lang/de.yml b/lang/de.yml index c03d14d..4ce7cd7 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -1,7 +1,7 @@ de: - SilverStripe\DocumentConverter\ConvertsDocumentsExtension: + SilverStripe\DocumentConverter\PageExtension: ImportTab: Importieren - SilverStripe\DocumentConverter\DocumentConversionField: + SilverStripe\DocumentConverter\SettingsField: ChooseFolder: 'Wählen Sie einen Ordner zum Speichern dieser Datei aus' EachH1: 'für jede Überschrift 1' EachH2: 'für jede Überschrift 2' diff --git a/lang/en.yml b/lang/en.yml index a2b125c..e88fee9 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -1,10 +1,10 @@ en: - SilverStripe\DocumentConverter\DocumentConverter: + SilverStripe\DocumentConverter\ServiceConnector: PROCESSFAILED: 'Could not process document, please double-check you uploaded a .doc or .docx format.' SERVERUNREACHABLE: 'Could not contact document conversion server. Please try again later or contact your system administrator.' - SilverStripe\DocumentConverter\ConvertsDocumentsExtension: + SilverStripe\DocumentConverter\PageExtension: ImportTab: Import - SilverStripe\DocumentConverter\DocumentConversionField: + SilverStripe\DocumentConverter\SettingsField: ChooseFolder: 'Choose a folder to save this file' EachH1: 'for each heading 1' EachH2: 'for each heading 2' @@ -15,7 +15,7 @@ en: 'No': 'no' SplitHeader: 'Split document into pages' publishPages: 'Publish modified pages (not recommended unless you are sure about the conversion outcome)' - SilverStripe\DocumentConverter\DocumentImporterField: + SilverStripe\DocumentConverter\ImportField: ATTACHONCESAVED2: 'Files can be attached once you have saved the record for the first time.' UploadField: ATTACHFILE: 'Attach a file' diff --git a/lang/eo.yml b/lang/eo.yml index ff7961a..c37e151 100644 --- a/lang/eo.yml +++ b/lang/eo.yml @@ -1,10 +1,10 @@ eo: - SilverStripe\DocumentConverter\DocumentConverter: + SilverStripe\DocumentConverter\ServiceConnector: PROCESSFAILED: 'Ne povis trakti dokumenton. Bonvole kontrolu ke vi alŝutis en formato .doc aŭ .docx.' SERVERUNREACHABLE: 'Ne povis kontakti servilon por konverti dokumenton. Bonvole reprovu postiome aŭ kontaktu vian administranton.' - SilverStripe\DocumentConverter\ConvertsDocumentsExtension: + SilverStripe\DocumentConverter\PageExtension: ImportTab: Importi - SilverStripe\DocumentConverter\DocumentConversionField: + SilverStripe\DocumentConverter\SettingsField: ChooseFolder: 'Elektu dosierujon en kiu konservi la dosieron' EachH1: 'por ĉiu titolo 1' EachH2: 'por ĉiu titolo 2' @@ -15,7 +15,7 @@ eo: 'No': ne SplitHeader: 'Dividi dokumenton en paĝojn' publishPages: 'Publikigi ŝanĝitajn paĝojn (ne rekomendinde krom se vi certas pri la rezulto de konverto)' - SilverStripe\DocumentConverter\DocumentImporterField: + SilverStripe\DocumentConverter\ImportField: ATTACHONCESAVED2: 'Kiam vi unuafoje estos konservinta la rikordon vi povos alligi dosierojn.' UploadField: ATTACHFILE: 'Alligi dosieron' diff --git a/lang/fa_IR.yml b/lang/fa_IR.yml index a98ba83..d678140 100644 --- a/lang/fa_IR.yml +++ b/lang/fa_IR.yml @@ -1,7 +1,7 @@ fa_IR: - SilverStripe\DocumentConverter\ConvertsDocumentsExtension: + SilverStripe\DocumentConverter\PageExtension: ImportTab: وارد کردن - SilverStripe\DocumentConverter\DocumentConversionField: + SilverStripe\DocumentConverter\SettingsField: ChooseFolder: 'یک پوشه برای ذخیره این فایل را انتخاب کنید' UploadField: ATTACHFILE: 'یک فایل ضمیمه کنید' diff --git a/lang/fi.yml b/lang/fi.yml index 2432796..cda8648 100644 --- a/lang/fi.yml +++ b/lang/fi.yml @@ -1,10 +1,10 @@ fi: - SilverStripe\DocumentConverter\DocumentConverter: + SilverStripe\DocumentConverter\ServiceConnector: PROCESSFAILED: 'Dokumenttia ei voitu prosessoida, ole hyvä ja tarkista että siirtämäsi dokumentit olivat varmasti .doc tai .docx muodossa.' SERVERUNREACHABLE: 'Muunnospalvelimeen ei saatu yhteyttä. Ole hyvä ja yritä myöhemmin uudelleen tai ota yhteyttä järjestelmän ylläpitäjään.' - SilverStripe\DocumentConverter\ConvertsDocumentsExtension: + SilverStripe\DocumentConverter\PageExtension: ImportTab: Tuo - SilverStripe\DocumentConverter\DocumentConversionField: + SilverStripe\DocumentConverter\SettingsField: ChooseFolder: 'Valitse kansio, jonne tiedosto tallennetaan' EachH1: 'jokaiselle otsikko 1' EachH2: 'jokaiselle otsikko 2' @@ -15,7 +15,7 @@ fi: 'No': ei SplitHeader: 'Jaa dokumentti sivuiksi' publishPages: 'Julkaise muokatut sivut (ei suositella, jollet ole varma muunnoksen lopputulemasta)' - SilverStripe\DocumentConverter\DocumentImporterField: + SilverStripe\DocumentConverter\ImportField: ATTACHONCESAVED2: 'Tiedostot voidaan liittää, kun olet tallentanut ensin tietueen.' UploadField: ATTACHFILE: 'Liitä tiedosto' diff --git a/lang/id.yml b/lang/id.yml index f0683c6..97850e8 100644 --- a/lang/id.yml +++ b/lang/id.yml @@ -1,7 +1,7 @@ id: - SilverStripe\DocumentConverter\ConvertsDocumentsExtension: + SilverStripe\DocumentConverter\PageExtension: ImportTab: Impor - SilverStripe\DocumentConverter\DocumentConversionField: + SilverStripe\DocumentConverter\SettingsField: ChooseFolder: 'Pilih sebuah map untuk menyimpan berkas ini' UploadField: FROMCOMPUTER: 'Dari komputermu' diff --git a/lang/mi.yml b/lang/mi.yml index 67ed3fa..d4c05ae 100644 --- a/lang/mi.yml +++ b/lang/mi.yml @@ -1,10 +1,10 @@ mi: - SilverStripe\DocumentConverter\DocumentConverter: + SilverStripe\DocumentConverter\ServiceConnector: PROCESSFAILED: 'Kāore i taea te tukatuka te tuhinga, whakatūturu pūtia kua tukuna atu he hōputu .doc, .docx rānei.' SERVERUNREACHABLE: 'Kāore i taea te whakapā ki te tūmau huri tuhinga. Me ngana anō ā muri ake, ka whakapā rānei ki tō kaiwhakahaere pūnaha.' - DocumentCSilverStripe\DocumentConverter\ConvertsDocumentsExtension: + DocumentCSilverStripe\DocumentConverter\PageExtension: ImportTab: Kawemai - SilverStripe\DocumentConverter\DocumentImporterField: + SilverStripe\DocumentConverter\ImportField: ATTACHONCESAVED2: 'Ka taea te āpiti kōnae ina oti te tiaki tuatahi o te pūkete.' UploadField: ATTACHFILE: 'Tāpiritia tētahi kōnae' diff --git a/lang/ru.yml b/lang/ru.yml index 5158ff1..aa93319 100644 --- a/lang/ru.yml +++ b/lang/ru.yml @@ -1,10 +1,10 @@ ru: - SilverStripe\DocumentConverter\DocumentConverter: + SilverStripe\DocumentConverter\ServiceConnector: PROCESSFAILED: 'Не могу обработать документ, пожалуйста проверьте что документ загружен в формате .doc или .docx.' SERVERUNREACHABLE: 'Не могу подключиться к серверу конвертации документов. Пожалуйста попробуйте позднее или свяжитесь с вашим системным администратором.' - DocumentConverterDecorator: + SilverStripe\DocumentConverter\PageExtension: ImportTab: Импорт - DocumentImportField: + SilverStripe\DocumentConverter\SettingsField: ChooseFolder: 'Выберите папку для сохранения файла' EachH1: 'для каждого заголовка 1' EachH2: 'для каждого заголовка 2' @@ -15,7 +15,7 @@ ru: 'No': нет SplitHeader: 'Разделить документ на страницы' publishPages: 'Опубликовать модифицированные страницы (не рекомендуется если вы не уверены в последствиях)' - SilverStripe\DocumentConverter\DocumentImporterField: + SilverStripe\DocumentConverter\ImportField: ATTACHONCESAVED2: 'Вы сможете прикрепитьфайлы после первого сохранения записи.' UploadField: ATTACHFILE: 'Прикрепить файл' diff --git a/lang/zh.yml b/lang/zh.yml index 4caab4c..70dcbba 100644 --- a/lang/zh.yml +++ b/lang/zh.yml @@ -1,10 +1,10 @@ zh: - SilverStripe\DocumentConverter\DocumentConverter: + SilverStripe\DocumentConverter\ServiceConnector: PROCESSFAILED: '无法处理文档,请复核您上传的是 .doc 或 .docx 格式。' SERVERUNREACHABLE: '无法连到文档转换服务器。请稍后再试或联系系统管理员。' - SilverStripe\DocumentConverter\ConvertsDocumentsExtension: + SilverStripe\DocumentConverter\PageExtension: ImportTab: 导入 - SilverStripe\DocumentConverter\DocumentConversionField: + SilverStripe\DocumentConverter\SettingsField: ChooseFolder: '选择文件夹保存此文件' EachH1: '每个标题1' EachH2: '每个标题2' @@ -14,7 +14,7 @@ zh: KeepSource: '保留原始文档。如果允许的话,在 TOC 上为其添加一个链接。' SplitHeader: 拆分文件转换成网页 publishPages: '发布修改页面(除非你确认转换结果,否则不推荐)' - SilverStripe\DocumentConverter\DocumentImporterField: + SilverStripe\DocumentConverter\ImportField: ATTACHONCESAVED2: '只要您第一次保存记录,文件就可以被附加。' UploadField: ATTACHFILE: '附加一个文件' diff --git a/src/ConvertsDocumentsExtension.php b/src/ConvertsDocumentsExtension.php deleted file mode 100644 index 3c8df15..0000000 --- a/src/ConvertsDocumentsExtension.php +++ /dev/null @@ -1,31 +0,0 @@ - File::class - ); - - function updateCMSFields(FieldList $fields) { - /* - // Currently the ToggleCompositeField plays badly with TreeDropdownField formatting. - // Could be switched back in the future, if this is fixed. - $fields->addFieldToTab('Root.Main', - ToggleCompositeField::create('Import', 'Import', array( - new DocumentImportField() - ))->setHeadingLevel(4) - ); - */ - $fields->findOrMakeTab( - 'Root.Import', - _t(__CLASS__ . '.ImportTab', 'Import') - ); - $fields->addFieldToTab('Root.Import', DocumentConversionField::create()); - } -} diff --git a/src/DocumentConversionField.php b/src/DocumentConversionField.php deleted file mode 100644 index 843cbb5..0000000 --- a/src/DocumentConversionField.php +++ /dev/null @@ -1,101 +0,0 @@ -setName() if you want to change it.'); - if ($children) throw new InvalidArgumentException('DocumentConversionField::__construct provides its own fields and does not accept additional children.'); - - // Add JS specific to this field. - Requirements::javascript('silverstripe/documentconverter: javascript/DocumentConversionField.js'); - - $fields = FieldList::create(array( - HeaderField::create( - 'FileWarningHeader', - _t( - __CLASS__ . '.FileWarningHeader', - 'Warning: import will remove all content and subpages of this page.' - ), - 4 - ), - $splitHeader = DropdownField::create( - 'DocumentConversionField-SplitHeader', - _t( - __CLASS__ . '.SplitHeader', - 'Split document into pages' - ), - array( - 0 => _t(__CLASS__ . '.No','no'), - 1 => _t(__CLASS__ . '.EachH1','for each heading 1'), - 2 => _t(__CLASS__ . '.EachH2','for each heading 2') - ) - ), - $keepSource = CheckboxField::create( - 'DocumentConversionField-KeepSource', - _t( - __CLASS__ . '.KeepSource', - 'Keep the original document. Adds a link to it on TOC, if enabled.' - ) - ), - $chosenFolderID = TreeDropdownField::create( - 'DocumentConversionField-ChosenFolderID', - _t(__CLASS__ . '.ChooseFolder', 'Choose a folder to save this file'), - Folder::class - ), - $includeTOC = CheckboxField::create( - 'DocumentConversionField-IncludeTOC', - _t(__CLASS__ . '.IncludeTOC', 'Replace this page with a Table of Contents.') - ), - $publishPages = CheckboxField::create( - 'DocumentConversionField-PublishPages', - _t( - __CLASS__ . '.publishPages', - 'Publish modified pages (not recommended unless you are sure about the conversion outcome)' - ) - ), - $this->innerField = DocumentImporterField::create( - 'ImportedFromFile', - _t(__CLASS__ . '.ImportedFromFile','Import content from a word document') - ), - )); - - // Prevent the warning popup that appears when navigating away from the page. - $splitHeader->addExtraClass('no-change-track'); - $keepSource->addExtraClass('no-change-track'); - $chosenFolderID->addExtraClass('no-change-track'); - $includeTOC->addExtraClass('no-change-track'); - $publishPages->addExtraClass('no-change-track'); - - return parent::__construct($fields); - } - - public function getInnerField() { - return $this->innerField; - } -} diff --git a/src/DocumentConverter.php b/src/DocumentConverter.php deleted file mode 100644 index 74fe6f3..0000000 --- a/src/DocumentConverter.php +++ /dev/null @@ -1,153 +0,0 @@ - '', - 'password' => '', - 'url' => '' - ]; - - /** - * Associative array of: - * - name: the full name of the file including the extension. - * - path: the path to the file on the local filesystem. - * - mimeType - */ - protected $fileDescriptor; - - /** - * @var int - * ID of a SilverStripe\Assets\Folder - */ - protected $chosenFolderID; - - /** - * @var array instance specific connection details - * initially filled with the config settings - */ - protected $docvertDetails = [ - 'username' => '', - 'password' => '', - 'url' => '' - ]; - - public function __construct($fileDescriptor, $chosenFolderID = null) { - $this->fileDescriptor = $fileDescriptor; - $this->chosenFolderID = $chosenFolderID; - array_merge($this->docvertDetails, (array)$this->config()->get('docvert_details')); - } - - public function setDocvertUsername($username = null) { - $this->docvertDetails['username'] = $username; - } - - public function getDocvertUsername() { - return $this->docvertDetails['username']; - } - - public function setDocvertPassword($password = null) { - $this->docvertDetails['password'] = $password; - } - - public function getDocvertPassword() { - return $this->docvertDetails['password']; - } - - public function setDocvertUrl($url = null) { - $this->docvertDetails['url'] = $url; - } - - public function getDocvertUrl() { - return $this->docvertDetails['url']; - } - - public function import() { - $ch = curl_init(); - - // PHP 5.5+ introduced CURLFile which makes the '@/path/to/file' syntax deprecated. - if(class_exists('CURLFile')) { - $file = new CURLFile( - $this->fileDescriptor['path'], - $this->fileDescriptor['mimeType'], - $this->fileDescriptor['name'] - ); - } else { - $file = '@' . $this->fileDescriptor['path']; - } - - curl_setopt_array($ch, array( - CURLOPT_URL => $this->getDocvertUrl(), - CURLOPT_USERPWD => sprintf('%s:%s', $this->getDocvertUsername(), $this->getDocvertPassword()), - CURLOPT_POST => 1, - CURLOPT_POSTFIELDS => array('file' => $file), - CURLOPT_CONNECTTIMEOUT => 25, - CURLOPT_TIMEOUT => 100, - )); - - $chosenFolder = ($this->chosenFolderID) ? DataObject::get_by_id(Folder::class, $this->chosenFolderID) : null; - $folderName = ($chosenFolder) ? '/' . $chosenFolder->Name : ''; - $outname = tempnam(ASSETS_PATH, 'convert'); - $outzip = $outname . '.zip'; - - $out = fopen($outzip, 'w'); - curl_setopt($ch, CURLOPT_FILE, $out); - $returnValue = curl_exec($ch); - $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - fclose($out); - chmod($outzip, 0666); - - if (!$returnValue || ($status != 200)) { - return array('error' => _t( - __CLASS__ . '.SERVERUNREACHABLE', - 'Could not contact document conversion server. Please try again later or contact your system administrator.', - 'Document Converter process Word documents into HTML.' - )); - } - - // extract the converted document into assets - // you need php zip, i.e. port install php5-zip - $zip = new ZipArchive(); - - if($zip->open($outzip)) { - $zip->extractTo(ASSETS_PATH .$folderName); - } - - // remove temporary files - unlink($outname); - unlink($outzip); - - if (!file_exists(ASSETS_PATH . $folderName . '/index.html')) { - return array('error' => _t( - __CLASS__ . '.PROCESSFAILED', - 'Could not process document, please double-check you uploaded a .doc or .docx format.', - 'Document Converter processes Word documents into HTML.' - )); - } - - $content = file_get_contents(ASSETS_PATH . $folderName . '/index.html'); - - unlink(ASSETS_PATH . $folderName . '/index.html'); - - return $content; - } - -} diff --git a/src/DocumentImporterField.php b/src/DocumentImporterField.php deleted file mode 100644 index 67fdb51..0000000 --- a/src/DocumentImporterField.php +++ /dev/null @@ -1,434 +0,0 @@ -isDisabled() || $this->isReadonly()) return $this->httpError(403); - - // Protect against CSRF on destructive action - $token = $this->getForm()->getSecurityToken(); - if(!$token->checkRequest($request)) return $this->httpError(400); - - $name = $this->getName(); - $tmpfile = $request->postVar($name); - - // Check if the file has been uploaded into the temporary storage. - if (!$tmpfile) { - $return = array('error' => _t('SilverStripe\\AssetAdmin\\Forms\\UploadField.FIELDNOTSET', 'File information not found')); - } else { - $return = array( - 'name' => $tmpfile['name'], - 'size' => $tmpfile['size'], - 'type' => $tmpfile['type'], - 'error' => $tmpfile['error'] - ); - } - - if (!$return['error']) { - // Get options for this import. - $splitHeader = (int)$request->postVar('SplitHeader'); - $keepSource = (bool)$request->postVar('KeepSource'); - $chosenFolderID = (int)$request->postVar('ChosenFolderID'); - $publishPages = (bool)$request->postVar('PublishPages'); - $includeTOC = (bool)$request->postVar('IncludeTOC'); - - // Process the document and write the page. - $preservedDocument = null; - if ($keepSource) $preservedDocument = $this->preserveSourceDocument($tmpfile, $chosenFolderID); - - $importResult = $this->importFromPOST($tmpfile, $splitHeader, $publishPages, $chosenFolderID); - if (is_array($importResult) && isset($importResult['error'])) { - $return['error'] = $importResult['error']; - } else if ($includeTOC) { - $this->writeTOC($publishPages, $keepSource ? $preservedDocument : null); - } - } - - $response = HTTPResponse::create(Convert::raw2json(array($return))); - $response->addHeader('Content-Type', 'text/plain'); - return $response; - } - - /** - * Preserves the source file by copying it to a specified folder. - * - * @param $tmpfile Temporary file data structure. - * @param int $chosenFolderID Target folder. - * @return File Stored file. - */ - protected function preserveSourceDocument($tmpfile, $chosenFolderID = null) { - $upload = Upload::create(); - - $file = File::create(); - $upload->loadIntoFile($tmpfile, $file, $chosenFolderID); - - $page = $this->form->getRecord(); - $page->ImportedFromFileID = $file->ID; - $page->write(); - - return $file; - } - - /** - * Builds and writes the table of contents for the document. - * - * @param bool $publishPage Should the parent page be published. - * @param File $preservedDocument Set if the link to the original document should be added. - */ - protected function writeTOC($publishPages = false, $preservedDocument = null) { - $page = $this->form->getRecord(); - $content = ''; - } else { - $doc = new DOMDocument(); - $doc->loadHTML($page->Content); - $body = $doc->getElementsByTagName('body')->item(0); - $node = $body->firstChild; - $h1 = $h2 = 1; - while($node) { - if($node instanceof DOMElement && $node->tagName == 'h1') { - $content .= '
  • '. trim(preg_replace('/\n|\r/', '', Convert::html2raw($node->textContent))) . '
  • '; - $node->setAttributeNode(new DOMAttr("id", "h1.".$h1)); - $h1++; - } elseif($node instanceof DOMElement && $node->tagName == 'h2') { - $content .= ''; - $node->setAttributeNode(new DOMAttr("id", "h2.".$h2)); - $h2++; - } - $node = $node->nextSibling; - } - $page->Content = $content . '' . $doc->saveHTML(); - } - - // Add in the link to the original document, if provided. - if($preservedDocument) { - $page->Content = 'download original document (' . - $preservedDocument->getSize() . ')' . $page->Content; - } - - // Store the result - $page->write(); - if($publishPages) $page->doPublish(); - } - } - - protected function getBodyText($doc, $node) { - // Build a new doc - $htmldoc = new DOMDocument(); - // Create the html element - $html = $htmldoc->createElement('html'); - $htmldoc->appendChild($html); - // Append the body node - $html->appendChild($htmldoc->importNode($node, true)); - - // Get the text as html, remove the entry and exit root tags and return - $text = $htmldoc->saveHTML(); - $text = preg_replace('/^.*/', '', $text); - $text = preg_replace('/<\/body>.*$/', '', $text); - - return $text; - } - - /** - * Used only when writing the document that has been split by headers. - * Can write both to the chapter pages as well as the master page. - * - * @param string $subtitle Title of the chapter - if missing, it will write to the master page. - * @param $subdoc - * @param $subnode - * @param int $sort Order of the chapter page. - * @param $publishPages Whether to publish the resulting child/master pages. - */ - protected function writeContent($subtitle, $subdoc, $subnode, $sort = null, $publishPages = false) { - $record = $this->form->getRecord(); - - if($subtitle) { - // Write the chapter page to a subpage. - $page = DataObject::get_one('Page', sprintf('"Title" = \'%s\' AND "ParentID" = %d', $subtitle, $record->ID)); - if(!$page) { - $page = Page::create(); - $page->ParentID = $record->ID; - $page->Title = $subtitle; - } - - unset($this->unusedChildren[$page->ID]); - file_put_contents(ASSETS_PATH . '/index-' . $sort . '.html', $this->getBodyText($subdoc, $subnode)); - - if ($sort) $page->Sort = $sort; - $page->Content = $this->getBodyText($subdoc, $subnode); - $page->write(); - if($publishPages) $page->doPublish(); - } else { - // Write to the master page. - $record->Content = $this->getBodyText($subdoc, $subnode); - $record->write(); - - if($publishPages) $record->doPublish(); - } - - } - - /** - * Imports a document at a certain path onto the current page and writes it. - * CAUTION: Overwrites any existing content on the page! - * - * @param array $tmpFile Array as received from PHP's POST upload. - * @param bool $splitHeader Heading level to split by. - * @param bool $publishPages Whether the underlying pages should be published after import. - * @param int $chosenFolderID ID of the working folder - here the converted file and images will be stored. - */ - public function importFromPOST($tmpFile, $splitHeader = false, $publishPages = false, $chosenFolderID = null) { - - $fileDescriptor = array( - 'name' => $tmpFile['name'], - 'path' => $tmpFile['tmp_name'], - 'mimeType' => $tmpFile['type'] - ); - - $sourcePage = $this->form->getRecord(); - $importerClass = Config::inst()->get(__CLASS__, 'importer_class'); - $importer = Injector::inst()->create($importerClass, $fileDescriptor, $chosenFolderID); - $content = $importer->import(); - - if (is_array($content) && isset($content['error'])) { - return $content; - } - - // Clean up with tidy (requires tidy module) - $tidy = new Tidy(); - $tidy->parseString($content, array('output-xhtml' => true), 'utf8'); - $tidy->cleanRepair(); - - $fragment = []; - foreach($tidy->body()->child as $child) { - $fragment[] = $child->value; - } - - $htmlValue = Injector::inst()->create('HTMLValue', implode("\n", $fragment)); - - // Sanitise - $santiser = Injector::inst()->create('HtmlEditorSanitiser', HtmlEditorConfig::get_active()); - $santiser->sanitise($htmlValue); - - // Load in the HTML - $doc = $htmlValue->getDocument(); - $xpath = new DOMXPath($doc); - - // make sure any images are added as Image records with a relative link to assets - $chosenFolder = ($this->chosenFolderID) ? DataObject::get_by_id(Folder::class, $this->chosenFolderID) : null; - $folderName = ($chosenFolder) ? '/' . $chosenFolder->Name : ''; - $imgs = $xpath->query('//img'); - for($i = 0; $i < $imgs->length; $i++) { - $img = $imgs->item($i); - $originalPath = 'assets/' . $folderName . '/' . $img->getAttribute('src'); - $name = FileNameFilter::create()->filter(basename($originalPath)); - - $image = Image::get()->filter(array('Name' => $name, 'ParentID' => (int) $chosenFolderID))->first(); - if(!($image && $image->exists())) { - $image = Image::create(); - $image->ParentID = (int) $chosenFolderID; - $image->Name = $name; - $image->write(); - } - - // make sure it's put in place correctly so Image record knows where it is. - // e.g. in the case of underscores being renamed to dashes. - @rename(Director::getAbsFile($originalPath), Director::getAbsFile($image->getFilename())); - - $img->setAttribute('src', $image->getFilename()); - } - - $remove_rules = array( - '//h1[.//font[not(@face)]]' => 'p', // Change any headers that contain font tags (other than font face tags) into p elements - '//font' // Remove any font tags - ); - - foreach($remove_rules as $rule => $parenttag) { - if(is_numeric($rule)) { - $rule = $parenttag; - $parenttag = null; - } - - $nodes = array(); - foreach($xpath->query($rule) as $node) $nodes[] = $node; - - foreach($nodes as $node) { - $parent = $node->parentNode; - - if($parenttag) { - $parent = $doc->createElement($parenttag); - $node->nextSibling ? $node->parentNode->insertBefore($parent, $node->nextSibling) : $node->parentNode->appendChild($parent); - } - - while($node->firstChild) $parent->appendChild($node->firstChild); - $node->parentNode->removeChild($node); - } - } - - // Strip style, class, lang attributes. - $els = $doc->getElementsByTagName('*'); - for ($i = 0; $i < $els->length; $i++) { - $el = $els->item($i); - $el->removeAttribute('class'); - $el->removeAttribute('style'); - $el->removeAttribute('lang'); - } - - $els = $doc->getElementsByTagName('*'); - - // Remove a bunch of unwanted elements - $clean = array( - '//p[not(descendant-or-self::text() | descendant-or-self::img)]', // Empty paragraphs - '//*[self::h1 | self::h2 | self::h3 | self::h4 | self::h5 | self::h6][not(descendant-or-self::text() | descendant-or-self::img)]', // Empty headers - '//a[not(@href)]', // Anchors - '//br' // BR tags - ); - - foreach($clean as $query) { - // First get all the nodes. Need to build array, as they'll disappear from the nodelist while we're deleteing them, causing the indexing - // to screw up. - $nodes = array(); - foreach($xpath->query($query) as $node) $nodes[] = $node; - - // Then remove them all - foreach ($nodes as $node) { if ($node->parentNode) $node->parentNode->removeChild($node); } - } - - // Now split the document into portions by H1 - $body = $doc->getElementsByTagName('body')->item(0); - - $this->unusedChildren = array(); - foreach($sourcePage->Children() as $child) { - $this->unusedChildren[$child->ID] = $child; - } - - $documentImporterFieldError; - - $documentImporterFieldErrorHandler = function ($errno, $errstr, $errfile, $errline) use ( $documentImporterFieldError ) { - $documentImporterFieldError = _t( - 'SilverStripe\\DocumentConverter\\DocumentConverter.PROCESSFAILED', - 'Could not process document, please double-check you uploaded a .doc or .docx format.', - 'Document Converter processes Word documents into HTML.' - ); - - // Do not cascade the error through other handlers - return true; - }; - - set_error_handler($documentImporterFieldErrorHandler); - - $subtitle = null; - $subdoc = new DOMDocument(); - $subnode = $subdoc->createElement('body'); - $node = $body->firstChild; - $sort = 1; - if($splitHeader == 1 || $splitHeader == 2) { - while($node && !$documentImporterFieldError) { - if($node instanceof DOMElement && $node->tagName == 'h' . $splitHeader) { - if($subnode->hasChildNodes()) { - $this->writeContent($subtitle, $subdoc, $subnode, $sort, $publishPages); - $sort++; - } - - $subdoc = new DOMDocument(); - $subnode = $subdoc->createElement('body'); - $subtitle = trim(preg_replace('/\n|\r/', '', Convert::html2raw($node->textContent))); - } else { - $subnode->appendChild($subdoc->importNode($node, true)); - } - - $node = $node->nextSibling; - } - } else { - $this->writeContent($subtitle, $subdoc, $body, null, $publishPages); - } - - if($subnode->hasChildNodes() && !$documentImporterFieldError) { - $this->writeContent($subtitle, $subdoc, $subnode, null, $publishPages); - } - - restore_error_handler(); - if ($documentImporterFieldError) { - return array('error' => $documentImporterFieldError); - } - - foreach($this->unusedChildren as $child) { - $origStage = Versioned::current_stage(); - - Versioned::reading_stage('Stage'); - $clone = clone $child; - $clone->delete(); - - Versioned::reading_stage('Live'); - $clone = clone $child; - $clone->delete(); - - Versioned::reading_stage($origStage); - } - - $sourcePage->write(); - } -} diff --git a/src/ImportField.php b/src/ImportField.php new file mode 100644 index 0000000..2620750 --- /dev/null +++ b/src/ImportField.php @@ -0,0 +1,504 @@ +isDisabled() || $this->isReadonly()) { + return $this->httpError(403); + } + + // Protect against CSRF on destructive action + $token = $this->getForm()->getSecurityToken(); + if (!$token->checkRequest($request)) { + return $this->httpError(400); + } + + $tmpfile = $request->postVar('Upload'); + + // Check if the file has been uploaded into the temporary storage. + if (!$tmpfile) { + $return = [ + 'error' => _t( + 'SilverStripe\\AssetAdmin\\Forms\\UploadField.FIELDNOTSET', + 'File information not found' + ) + ]; + } else { + $return = [ + 'name' => $tmpfile['name'], + 'size' => $tmpfile['size'], + 'type' => $tmpfile['type'], + 'error' => $tmpfile['error'] + ]; + } + + if (!$return['error']) { + // Get options for this import. + $splitHeader = (int)$request->postVar('SplitHeader'); + $keepSource = (bool)$request->postVar('KeepSource'); + $chosenFolderID = (int)$request->postVar('ChosenFolderID'); + $publishPages = (bool)$request->postVar('PublishPages'); + $includeTOC = (bool)$request->postVar('IncludeTOC'); + + // Process the document and write the page. + $preservedDocument = null; + if ($keepSource) { + $preservedDocument = $this->preserveSourceDocument($tmpfile, $chosenFolderID); + } + + $importResult = $this->importFromPOST($tmpfile, $splitHeader, $publishPages, $chosenFolderID); + if (is_array($importResult) && isset($importResult['error'])) { + $return['error'] = $importResult['error']; + } elseif ($includeTOC) { + $this->writeTOC($publishPages, $keepSource ? $preservedDocument : null); + } + } + + $response = HTTPResponse::create(Convert::raw2json([$return])); + $response->addHeader('Content-Type', 'application/json'); + return $response; + } + + /** + * Preserves the source file by copying it to a specified folder. + * + * @param $tmpfile Temporary file data structure. + * @param int $chosenFolderID Target folder. + * @return File Stored file. + */ + protected function preserveSourceDocument($tmpfile, $chosenFolderID = null) + { + $upload = Upload::create(); + + $file = File::create(); + $upload->loadIntoFile($tmpfile, $file, $chosenFolderID); + + $page = $this->form->getRecord(); + $page->ImportedFromFileID = $file->ID; + $page->write(); + + return $file; + } + + /** + * Builds and writes the table of contents for the document. + * + * @param bool $publishPage Should the parent page be published. + * @param File $preservedDocument Set if the link to the original document should be added. + */ + protected function writeTOC($publishPages = false, $preservedDocument = null) + { + $page = $this->form->getRecord(); + $content = ''; + } else { + $doc = new DOMDocument(); + $doc->loadHTML($page->Content); + $body = $doc->getElementsByTagName('body')->item(0); + $node = $body->firstChild; + $h1 = $h2 = 1; + while ($node) { + if ($node instanceof DOMElement && $node->tagName == 'h1') { + $content .= '
  • ' . + trim(preg_replace('/\n|\r/', '', Convert::html2raw($node->textContent))) . + '
  • '; + $node->setAttributeNode(new DOMAttr("id", "h1.".$h1)); + $h1++; + } elseif ($node instanceof DOMElement && $node->tagName == 'h2') { + $content .= ''; + $node->setAttributeNode(new DOMAttr("id", "h2.".$h2)); + $h2++; + } + $node = $node->nextSibling; + } + $page->Content = $content . '' . $doc->saveHTML(); + } + + // Add in the link to the original document, if provided. + if ($preservedDocument) { + $page->Content = 'download original document (' . + $preservedDocument->getSize() . + ')' . + $page->Content; + } + + // Store the result + $page->write(); + if ($publishPages) { + $page->doPublish(); + } + } + } + + protected function getBodyText($doc, $node) + { + // Build a new doc + $htmldoc = new DOMDocument(); + // Create the html element + $html = $htmldoc->createElement('html'); + $htmldoc->appendChild($html); + // Append the body node + $html->appendChild($htmldoc->importNode($node, true)); + + // Get the text as html, remove the entry and exit root tags and return + $text = $htmldoc->saveHTML(); + $text = preg_replace('/^.*/', '', $text); + $text = preg_replace('/<\/body>.*$/', '', $text); + + return $text; + } + + /** + * Used only when writing the document that has been split by headers. + * Can write both to the chapter pages as well as the master page. + * + * @param string $subtitle Title of the chapter - if missing, it will write to the master page. + * @param $subdoc + * @param $subnode + * @param int $sort Order of the chapter page. + * @param $publishPages Whether to publish the resulting child/master pages. + */ + protected function writeContent($subtitle, $subdoc, $subnode, $sort = null, $publishPages = false) + { + $record = $this->form->getRecord(); + + if ($subtitle) { + // Write the chapter page to a subpage. + $page = DataObject::get_one( + 'Page', + sprintf('"Title" = \'%s\' AND "ParentID" = %d', $subtitle, $record->ID) + ); + if (!$page) { + $page = Page::create(); + $page->ParentID = $record->ID; + $page->Title = $subtitle; + } + + unset($this->unusedChildren[$page->ID]); + file_put_contents(ASSETS_PATH . '/index-' . $sort . '.html', $this->getBodyText($subdoc, $subnode)); + + if ($sort) { + $page->Sort = $sort; + } + $page->Content = $this->getBodyText($subdoc, $subnode); + $page->write(); + if ($publishPages) { + $page->doPublish(); + } + } else { + // Write to the master page. + $record->Content = $this->getBodyText($subdoc, $subnode); + $record->write(); + + if ($publishPages) { + $record->doPublish(); + } + } + } + + /** + * Imports a document at a certain path onto the current page and writes it. + * CAUTION: Overwrites any existing content on the page! + * + * @param array $tmpFile Array as received from PHP's POST upload. + * @param bool $splitHeader Heading level to split by. + * @param bool $publishPages Whether the underlying pages should be published after import. + * @param int $chosenFolderID ID of the working folder - here the converted file and images will be stored. + */ + public function importFromPOST($tmpFile, $splitHeader = false, $publishPages = false, $chosenFolderID = null) + { + + $fileDescriptor = [ + 'name' => $tmpFile['name'], + 'path' => $tmpFile['tmp_name'], + 'mimeType' => $tmpFile['type'] + ]; + + $sourcePage = $this->form->getRecord(); + $importerClass = $this->config()->get('importer_class'); + $importer = Injector::inst()->create($importerClass, $fileDescriptor, $chosenFolderID); + $content = $importer->import(); + + if (is_array($content) && isset($content['error'])) { + return $content; + } + + // Clean up with tidy (requires tidy module) + $tidy = new Tidy(); + $tidy->parseString($content, ['output-xhtml' => true], 'utf8'); + $tidy->cleanRepair(); + + $fragment = []; + foreach ($tidy->body()->child as $child) { + $fragment[] = $child->value; + } + + $htmlValue = Injector::inst()->create(HTMLValue::class, implode("\n", $fragment)); + + // Sanitise + $santiser = Injector::inst()->create(HTMLEditorSanitiser::class, HTMLEditorConfig::get_active()); + $santiser->sanitise($htmlValue); + + // Load in the HTML + $doc = $htmlValue->getDocument(); + $xpath = new DOMXPath($doc); + + // make sure any images are added as Image records with a relative link to assets + $chosenFolder = ($this->chosenFolderID) ? DataObject::get_by_id(Folder::class, $this->chosenFolderID) : null; + $folderName = ($chosenFolder) ? '/' . $chosenFolder->Name : ''; + $imgs = $xpath->query('//img'); + for ($i = 0; $i < $imgs->length; $i++) { + $img = $imgs->item($i); + $originalPath = 'assets/' . $folderName . '/' . $img->getAttribute('src'); + $name = FileNameFilter::create()->filter(basename($originalPath)); + + $image = Image::get()->filter([ + 'Name' => $name, + 'ParentID' => (int)$chosenFolderID + ])->first(); + if (!($image && $image->exists())) { + $image = Image::create(); + $image->ParentID = (int)$chosenFolderID; + $image->Name = $name; + $image->write(); + } + + // make sure it's put in place correctly so Image record knows where it is. + // e.g. in the case of underscores being renamed to dashes. + @rename(Director::getAbsFile($originalPath), Director::getAbsFile($image->getFilename())); + + $img->setAttribute('src', $image->getFilename()); + } + + $remove_rules = [ + // Change any headers that contain font tags (other than font face tags) into p elements + '//h1[.//font[not(@face)]]' => 'p', + // Remove any font tags + '//font' + ]; + + foreach ($remove_rules as $rule => $parenttag) { + if (is_numeric($rule)) { + $rule = $parenttag; + $parenttag = null; + } + + $nodes = []; + foreach ($xpath->query($rule) as $node) { + $nodes[] = $node; + } + + foreach ($nodes as $node) { + $parent = $node->parentNode; + + if ($parenttag) { + $parent = $doc->createElement($parenttag); + $node->nextSibling ? + $node->parentNode->insertBefore($parent, $node->nextSibling) : + $node->parentNode->appendChild($parent); + } + + while ($node->firstChild) { + $parent->appendChild($node->firstChild); + } + $node->parentNode->removeChild($node); + } + } + + // Strip style, class, lang attributes. + $els = $doc->getElementsByTagName('*'); + for ($i = 0; $i < $els->length; $i++) { + $el = $els->item($i); + $el->removeAttribute('class'); + $el->removeAttribute('style'); + $el->removeAttribute('lang'); + } + + $els = $doc->getElementsByTagName('*'); + + $headingXPath = [ + 'self::h1', + 'self::h2', + 'self::h3', + 'self::h4', + 'self::h5', + 'self::h6', + ]; + // Remove a bunch of unwanted elements + $clean = [ + // Empty paragraphs + '//p[not(descendant-or-self::text() | descendant-or-self::img)]', + // Empty headers + '//*[' . implode(' | ', $headingXPath) . '][not(descendant-or-self::text() | descendant-or-self::img)]', + // Anchors + '//a[not(@href)]', + // BR tags + '//br' + ]; + + foreach ($clean as $query) { + // First get all the nodes. Need to build array, as they'll disappear from the + // nodelist while we're deleteing them, causing the indexing to screw up. + $nodes = []; + foreach ($xpath->query($query) as $node) { + $nodes[] = $node; + } + + // Then remove them all + foreach ($nodes as $node) { + if ($node->parentNode) { + $node->parentNode->removeChild($node); + } + } + } + + // Now split the document into portions by H1 + $body = $doc->getElementsByTagName('body')->item(0); + + $this->unusedChildren = []; + foreach ($sourcePage->Children() as $child) { + $this->unusedChildren[$child->ID] = $child; + } + + $documentImporterFieldError = false; + + $documentImporterFieldErrorHandler = function ( + $errno, + $errstr, + $errfile, + $errline + ) use ($documentImporterFieldError) { + $documentImporterFieldError = _t( + 'SilverStripe\\DocumentConverter\\ServiceConnector.PROCESSFAILED', + 'Could not process document, please double-check you uploaded a .doc or .docx format.', + 'Document Converter processes Word documents into HTML.' + ); + + // Do not cascade the error through other handlers + return true; + }; + + set_error_handler($documentImporterFieldErrorHandler); + + $subtitle = null; + $subdoc = new DOMDocument(); + $subnode = $subdoc->createElement('body'); + $node = $body->firstChild; + $sort = 1; + if ($splitHeader == 1 || $splitHeader == 2) { + while ($node && !$documentImporterFieldError) { + if ($node instanceof DOMElement && $node->tagName == 'h' . $splitHeader) { + if ($subnode->hasChildNodes()) { + $this->writeContent($subtitle, $subdoc, $subnode, $sort, $publishPages); + $sort++; + } + + $subdoc = new DOMDocument(); + $subnode = $subdoc->createElement('body'); + $subtitle = trim(preg_replace('/\n|\r/', '', Convert::html2raw($node->textContent))); + } else { + $subnode->appendChild($subdoc->importNode($node, true)); + } + + $node = $node->nextSibling; + } + } else { + $this->writeContent($subtitle, $subdoc, $body, null, $publishPages); + } + + if ($subnode->hasChildNodes() && !$documentImporterFieldError) { + $this->writeContent($subtitle, $subdoc, $subnode, null, $publishPages); + } + + restore_error_handler(); + if ($documentImporterFieldError) { + return ['error' => $documentImporterFieldError]; + } + + foreach ($this->unusedChildren as $child) { + $origStage = Versioned::current_stage(); + + Versioned::reading_stage('Stage'); + $draft = clone $child; + $draft->delete(); + + Versioned::reading_stage('Live'); + $published = clone $child; + $published->delete(); + + Versioned::reading_stage($origStage); + } + + $sourcePage->write(); + } +} diff --git a/src/PageExtension.php b/src/PageExtension.php new file mode 100644 index 0000000..1e780d4 --- /dev/null +++ b/src/PageExtension.php @@ -0,0 +1,33 @@ + File::class + ]; + + public function updateCMSFields(FieldList $fields) + { + /* + // Currently the ToggleCompositeField plays badly with TreeDropdownField formatting. + // Could be switched back in the future, if this is fixed. + $fields->addFieldToTab('Root.Main', + ToggleCompositeField::create('Import', 'Import', [ + SettingsField::create() + ])->setHeadingLevel(4) + ); + */ + $fields->findOrMakeTab( + 'Root.Import', + _t(__CLASS__ . '.ImportTab', 'Import') + ); + $fields->addFieldToTab('Root.Import', SettingsField::create()); + } +} diff --git a/src/ServiceConnector.php b/src/ServiceConnector.php new file mode 100644 index 0000000..e1aa102 --- /dev/null +++ b/src/ServiceConnector.php @@ -0,0 +1,207 @@ + null, + 'password' => null, + 'url' => null + ]; + + public function __construct($fileDescriptor, $chosenFolderID = null) + { + $this->fileDescriptor = $fileDescriptor; + $this->chosenFolderID = $chosenFolderID; + } + + public function setUsername($username = null) + { + $this->docvertDetails['username'] = $username; + } + + public function getUsername() + { + $username = $this->docvertDetails['username']; + if ($username) { + return $username; + } + $username = $this->config()->get('username'); + if ($username) { + return $username; + } + $username = Environment::getEnv('DOCVERT_USERNAME'); + if ($username) { + return $username; + } + return null; + } + + public function setPassword($password = null) + { + $this->docvertDetails['password'] = $password; + } + + public function getPassword() + { + $username = $this->docvertDetails['password']; + if ($username) { + return $username; + } + $username = $this->config()->get('password'); + if ($username) { + return $username; + } + $username = Environment::getEnv('DOCVERT_PASSWORD'); + if ($username) { + return $username; + } + return null; + } + + public function setUrl($url = null) + { + $this->docvertDetails['url'] = $url; + } + + public function getUrl() + { + $username = $this->docvertDetails['url']; + if ($username) { + return $username; + } + $username = $this->config()->get('url'); + if ($username) { + return $username; + } + $username = Environment::getEnv('DOCVERT_URL'); + if ($username) { + return $username; + } + return null; + } + + public function import() + { + $ch = curl_init(); + + // PHP 5.5+ introduced CURLFile which makes the '@/path/to/file' syntax deprecated. + if (class_exists('CURLFile')) { + $file = new CURLFile( + $this->fileDescriptor['path'], + $this->fileDescriptor['mimeType'], + $this->fileDescriptor['name'] + ); + } else { + $file = '@' . $this->fileDescriptor['path']; + } + + curl_setopt_array($ch, [ + CURLOPT_URL => $this->getUrl(), + CURLOPT_USERPWD => sprintf('%s:%s', $this->getUsername(), $this->getPassword()), + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => ['file' => $file], + CURLOPT_CONNECTTIMEOUT => 25, + CURLOPT_TIMEOUT => 100, + ]); + + $chosenFolder = ($this->chosenFolderID) ? DataObject::get_by_id(Folder::class, $this->chosenFolderID) : null; + $folderName = ($chosenFolder) ? '/' . $chosenFolder->Name : ''; + $outname = tempnam(ASSETS_PATH, 'convert'); + $outzip = $outname . '.zip'; + $out = fopen($outzip, 'w'); + curl_setopt($ch, CURLOPT_FILE, $out); + $returnValue = curl_exec($ch); + $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + fclose($out); + chmod($outzip, 0666); + + if (!$returnValue || ($status != 200)) { + return ['error' => _t( + __CLASS__ . '.SERVERUNREACHABLE', + 'Could not contact document conversion server. Please try again later ' . + 'or contact your system administrator.', + 'Document Converter process Word documents into HTML.' + )]; + } + + // extract the converted document into assets + // you need php zip, e.g. apt-get install php-zip + $zip = new ZipArchive(); + + if ($zip->open($outzip)) { + $zip->extractTo(ASSETS_PATH .$folderName); + $zip->close(); + } + + // remove temporary files + unlink($outname); + unlink($outzip); + + if (!file_exists(ASSETS_PATH . $folderName . '/index.html')) { + return ['error' => _t( + __CLASS__ . '.PROCESSFAILED', + 'Could not process document, please double-check you uploaded a .doc or .docx format.', + 'Document Converter processes Word documents into HTML.' + )]; + } + + $content = file_get_contents(ASSETS_PATH . $folderName . '/index.html'); + + unlink(ASSETS_PATH . $folderName . '/index.html'); + + return $content; + } +} diff --git a/src/SettingsField.php b/src/SettingsField.php new file mode 100644 index 0000000..9405798 --- /dev/null +++ b/src/SettingsField.php @@ -0,0 +1,108 @@ + _t(__CLASS__ . '.No', 'no'), + 1 => _t(__CLASS__ . '.EachH1', 'for each heading 1'), + 2 => _t(__CLASS__ . '.EachH2', 'for each heading 2') + ] + ), + $keepSource = CheckboxField::create( + 'DocumentConversionSettings-KeepSource', + _t( + __CLASS__ . '.KeepSource', + 'Keep the original document. Adds a link to it on TOC, if enabled.' + ) + ), + $chosenFolderID = TreeDropdownField::create( + 'DocumentConversionSettings-ChosenFolderID', + _t(__CLASS__ . '.ChooseFolder', 'Choose a folder to save this file'), + Folder::class + ), + $includeTOC = CheckboxField::create( + 'DocumentConversionSettings-IncludeTOC', + _t(__CLASS__ . '.IncludeTOC', 'Replace this page with a Table of Contents.') + ), + $publishPages = CheckboxField::create( + 'DocumentConversionSettings-PublishPages', + _t( + __CLASS__ . '.publishPages', + 'Publish modified pages (not recommended unless you are sure about the conversion outcome)' + ) + ), + $this->innerField = ImportField::create( + 'ImportedFromFile', + _t(__CLASS__ . '.ImportedFromFile', 'Import content from a word document') + ) + ]); + + // Prevent the warning popup that appears when navigating away from the page. + $splitHeader->addExtraClass('no-change-track'); + $keepSource->addExtraClass('no-change-track'); + $chosenFolderID->addExtraClass('no-change-track'); + $includeTOC->addExtraClass('no-change-track'); + $publishPages->addExtraClass('no-change-track'); + + return parent::__construct($fields); + } + + public function getInnerField() + { + return $this->innerField; + } +} diff --git a/tests/DocumentConverterDecoratorTest.php b/tests/DocumentConverterDecoratorTest.php deleted file mode 100644 index 37d0828..0000000 --- a/tests/DocumentConverterDecoratorTest.php +++ /dev/null @@ -1,19 +0,0 @@ - array( - 'DocumentConverterDecorator', - ), - ); - - public function testFieldListHasDocumentImportField() - { - $fields = (new SiteTree)->getCMSFields(); - $this->assertInstanceOf( - 'DocumentImportField', - $fields->fieldByName('Root.Import')->Fields()->First() - ); - } -} diff --git a/tests/DocumentConverterTest.php b/tests/DocumentConverterTest.php deleted file mode 100644 index dc422fe..0000000 --- a/tests/DocumentConverterTest.php +++ /dev/null @@ -1,17 +0,0 @@ -markTestIncomplete(); - } -} \ No newline at end of file diff --git a/tests/DocumentImportFieldTest.php b/tests/DocumentImportFieldTest.php deleted file mode 100644 index 0564a4d..0000000 --- a/tests/DocumentImportFieldTest.php +++ /dev/null @@ -1,51 +0,0 @@ -clear(); - - new DocumentImportField(); - $javascript = Requirements::backend()->get_javascript(); - $this->assertNotEmpty($javascript); - } - - public function testFieldListGeneration() - { - $importField = new DocumentImportField(); - - $fields = $importField->getChildren(); - $this->assertInstanceOf('FieldList', $fields); - - // We don't need to check that all of the fields are there, but just check a couple - $this->assertInstanceOf('HeaderField', $fields->fieldByName('FileWarningHeader')); - $innerField = $fields->fieldByName('ImportedFromFile'); - $this->assertInstanceOf('DocumentImportInnerField', $innerField); - - // Check the getter works - $this->assertSame($innerField, $importField->getInnerField()); - - // Check the fields have been given has the change tracker disabled - $splitHeader = $fields->fieldByName('DocumentImportField-SplitHeader'); - $this->assertInstanceOf('DropdownField', $splitHeader); - $this->assertContains('no-change-track', $splitHeader->extraClass()); - } -} diff --git a/tests/PageExtensionTest.php b/tests/PageExtensionTest.php new file mode 100644 index 0000000..bac5c60 --- /dev/null +++ b/tests/PageExtensionTest.php @@ -0,0 +1,25 @@ + [PageExtension::class] + ]; + + public function testFieldListHasDocumentImportField() + { + $siteTree = new SiteTree; + $fields = $siteTree->getCMSFields(); + $this->assertInstanceOf( + SettingsField::class, + $fields->fieldByName('Root.Import')->Fields()->First() + ); + } +} diff --git a/tests/SettingsFieldTest.php b/tests/SettingsFieldTest.php new file mode 100644 index 0000000..1498af2 --- /dev/null +++ b/tests/SettingsFieldTest.php @@ -0,0 +1,74 @@ +clear(); + + new SettingsField(); + $javascript = Requirements::backend()->getJavascript(); + $this->assertNotEmpty($javascript); + } + + public function testFieldListGeneration() + { + $importField = new SettingsField(); + + $fields = $importField->getChildren(); + $this->assertInstanceOf(FieldList::class, $fields); + + // We don't need to check that all of the fields are there, but just check a couple + $this->assertInstanceOf(HeaderField::class, $fields->fieldByName('FileWarningHeader')); + $innerField = $fields->fieldByName('ImportedFromFile'); + $this->assertInstanceOf(ImportField::class, $innerField); + + // Check the getter works + $this->assertSame($innerField, $importField->getInnerField()); + + // Check the fields have been given has the change tracker disabled + $settingsFields = [ + 'SplitHeader' => DropdownField::class, + 'KeepSource' => CheckboxField::class, + 'ChosenFolderID' => TreeDropdownField::class, + 'IncludeTOC' => CheckboxField::class, + 'PublishPages' => CheckboxField::class + ]; + foreach ($settingsFields as $fieldName => $className) { + $field = $fields->fieldByName( + 'DocumentConversionSettings-' . $fieldName + ); + $this->assertInstanceOf($className, $field); + $this->assertContains('no-change-track', $field->extraClass()); + } + } +} diff --git a/tests/Stubs/MockDocvertService.php b/tests/Stubs/MockDocvertService.php new file mode 100644 index 0000000..cc3540a --- /dev/null +++ b/tests/Stubs/MockDocvertService.php @@ -0,0 +1,14 @@ +Fake document

    For testing purposes.

    '; + } +} diff --git a/tests/Stubs/TestPage.php b/tests/Stubs/TestPage.php new file mode 100644 index 0000000..0b73610 --- /dev/null +++ b/tests/Stubs/TestPage.php @@ -0,0 +1,15 @@ + '

    Default TestPage

    With pre-import content.

    ' + ]; +}