new PDF writing API

This commit is contained in:
Roland Gruber 2017-07-02 09:00:14 +02:00
parent 3a4e208cf9
commit 642f24fcee
6 changed files with 242 additions and 225 deletions

View File

@ -71,62 +71,6 @@ function getPDFStructures($typeId, $profile = null) {
return $return;
}
/**
* Saves PDF structure to XML file in format: <name>.<typeId>.xml
*
* @param string $typeId account type
* @param string $name name of structure
* @return string "no perms" if access denied or "ok".
*/
function savePDFStructure($typeId, $name) {
if (!isValidPDFStructureName($name) || !preg_match('/[a-zA-Z]+/', $typeId)) {
return 'no perms';
}
$struct_file = dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/' . $name . '.' . $typeId . '.xml';
if(!is_writable(dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName())) {
return 'no perms';
}
else {
$handle = @fopen($struct_file,'w');
if (!$handle) return 'no perms';
$pdf_attributes = '';
foreach($_SESSION['currentPageDefinitions'] as $key => $value) {
$pdf_attributes .= ' ' . $key . '="' . $value . '"';
}
$file = '<pdf' . $pdf_attributes . ">\n";
foreach($_SESSION['currentPDFStructure'] as $entry) {
$ident = '';
for($i=0;$i<$entry['level'] -1;$i++) {
$ident .= "\t";
}
$attributes = '';
if(isset($entry['attributes']) && is_array($entry['attributes'])) {
foreach($entry['attributes'] as $key => $value) {
$attributes .= ' ' . strtolower($key) . '="' . $value . '"';
}
}
if($entry['type'] == 'open') {
$file .= $ident . '<' . strtolower($entry['tag']) . $attributes . ">\n";
}
elseif($entry['type'] == 'close') {
$file .= $ident . '</' . strtolower($entry['tag']) . ">\n";
}
elseif($entry['type'] == 'complete') {
if(isset($entry['value'])) {
$file .= $ident . '<' . strtolower($entry['tag']) . $attributes . '>' . $entry['value'] . '</' . strtolower($entry['tag']) . ">\n";
}
else {
$file .= $ident . '<' . strtolower($entry['tag']) . $attributes . " />\n";
}
}
}
$file .= "</pdf>";
fwrite($handle,$file);
fclose($handle);
return 'ok';
}
}
/**
* Deletes XML file with PDF structure definitions.
*
@ -291,7 +235,7 @@ function deletePDFLogo($name) {
* @return boolean is valid
*/
function isValidPDFStructureName($name) {
return preg_match('/[a-zA-Z0-9\-\_]+/',$name) === 1;
return preg_match('/^[a-z0-9\-\_]+$/i',$name) === 1;
}
/**
@ -394,7 +338,7 @@ class PDFStructureReader {
$xml->open($file);
$structure = new PDFStructure();
// open <pdf>
$xml->read();
@$xml->read();
if (!$xml->name == 'pdf') {
logNewMessage(LOG_ERR, 'Unknown tag name: ' . $xml->name);
throw new \LAMException(_('Unable to read PDF structure.'));
@ -464,6 +408,102 @@ class PDFStructureReader {
}
/**
* Writes PDF structures to files.
*
* @author Roland Gruber
*/
class PDFStructureWriter {
/**
* Writes the PDF structure to disk.
*
* @param string $typeId type ID
* @param string $name structure name
* @param PDFStructure $structure structure
*/
public function write($typeId, $name, $structure) {
$fileName = $this->getFileName($typeId, $name);
$xml = $this->getXML($structure);
$this->writeXML($xml, $fileName);
}
/**
* Writes the PDF structure to disk.
*
* @param string $typeId type ID
* @param string $name structure name
* @return string file name
*/
protected function getFileName($typeId, $name) {
if (!isValidPDFStructureName($name) || !preg_match('/[a-zA-Z]+/', $typeId)) {
throw new \LAMException(_('PDF structure name not valid'),
_('The name for that PDF-structure you submitted is not valid. A valid name must consist of the following characters: \'a-z\',\'A-Z\',\'0-9\',\'_\',\'-\'.'));
}
if(!is_writable(dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName())) {
throw new \LAMException(_('Could not save PDF structure, access denied.'));
}
return dirname(__FILE__) . '/../config/pdf/' . $_SESSION['config']->getName() . '/' . $name . '.' . $typeId . '.xml';
}
/**
* Returns the generated XML.
*
* @param PDFStructure $structure structure
* @return string XML
*/
public function getXML($structure) {
$writer = new \XMLWriter();
$writer->openMemory();
$writer->setIndent(true);
$writer->setIndentString("\t");
$writer->startElement('pdf');
$writer->writeAttribute('filename', $structure->getLogo());
$writer->writeAttribute('headline', $structure->getTitle());
$writer->writeAttribute('foldingmarks', $structure->getFoldingMarks());
foreach ($structure->getSections() as $section) {
if ($section instanceof PDFTextSection) {
$writer->startElement('text');
$writer->text($section->getText());
$writer->endElement();
}
else {
$writer->startElement('section');
if ($section->isAttributeTitle()) {
$writer->writeAttribute('name', '_' . $section->getPdfKey());
}
else {
$writer->writeAttribute('name', $section->getTitle());
}
foreach ($section->getEntries() as $entry) {
$writer->startElement('entry');
$writer->writeAttribute('name', $entry->getKey());
$writer->endElement();
}
$writer->endElement();
}
}
$writer->endElement();
return $writer->outputMemory();
}
/**
* Writes the XML to the given file.
*
* @param string $xml XML
* @param string $file file name
*/
protected function writeXML($xml, $file) {
$handle = @fopen($file,'w');
if (!$handle) {
throw new \LAMException(_('Could not save PDF structure, access denied.'));
}
fwrite($handle, $xml);
fclose($handle);
}
}
/**
* PDF structure
*

View File

@ -222,6 +222,12 @@ include '../main_header.php';
$container->addElement($message, true);
}
if (isset($_GET['loadFailed'])) {
$message = new htmlStatusMessage("ERROR", _("Unable to read PDF structure."), htmlspecialchars($_GET['name']));
$message->colspan = 10;
$container->addElement($message, true);
}
// new template
if (!empty($availableTypes)) {
$container->addElement(new htmlSubTitle(_('Create a new PDF structure')), true);

View File

@ -19,6 +19,7 @@ use LAM\PDF\PDFTextSection;
use LAM\PDF\PDFEntrySection;
use LAM\PDF\PDFStructure;
use LAM\PDF\PDFSectionEntry;
use LAM\PDF\PDFStructureWriter;
/*
$Id$
@ -87,9 +88,6 @@ if (!$_SESSION['ldap'] || !$_SESSION['ldap']->server()) {
// Write $_POST variables to $_GET when form was submitted via post
if (isset($_POST['type'])) {
$_GET = $_POST;
if ($_POST['pdfname'] == '') {
unset($_GET['pdfname']);
}
}
$typeManager = new \LAM\TYPES\TypeManager();
@ -106,173 +104,24 @@ if(isset($_GET['abort'])) {
exit;
}
// Check if pdfname is valid, then save current structure to file and go to
// main pdf structure page
$saveErrors = array();
if(isset($_GET['submit'])) {
if(!isset($_GET['pdfname']) || !preg_match('/[a-zA-Z0-9\-\_]+/',$_GET['pdfname'])) {
$saveErrors[] = array('ERROR', _('PDF structure name not valid'), _('The name for that PDF-structure you submitted is not valid. A valid name must consist of the following characters: \'a-z\',\'A-Z\',\'0-9\',\'_\',\'-\'.'));
}
else {
$return = \LAM\PDF\savePDFStructure($type->getId(), $_GET['pdfname']);
if($return == 'ok') {
metaRefresh('pdfmain.php?savedSuccessfully=' . $_GET['pdfname']);
exit;
}
elseif($return == 'no perms'){
$saveErrors[] = array('ERROR', _("Could not save PDF structure, access denied."), $_GET['pdfname']);
}
}
}
foreach ($_GET as $key => $value) {
// Move a section, static text field or value entry downwards
if (strpos($key, 'down_') === 0) {
$index = substr($key, strlen('down_'));
$tmp = $_SESSION['currentPDFStructure'][$index];
$next = $_SESSION['currentPDFStructure'][$index + 1];
// We have a section or static text to move
if($tmp['tag'] == 'SECTION' || $tmp['tag'] == 'TEXT') {
$pos = 0;
$current = current($_SESSION['currentPDFStructure']);
// Find section or static text entry to move
while($pos < $index) {
$current = next($_SESSION['currentPDFStructure']);
$pos++;
}
$borders = array();
// We have a section to move
if($current['tag'] == 'SECTION'){
$borders[$current['type']][] = $pos;
$current = next($_SESSION['currentPDFStructure']);
$pos++;
// Find end of section to move
while($current && $current['tag'] != 'SECTION' && $current['type'] != 'close') {
$current = next($_SESSION['currentPDFStructure']);
$pos++;
}
$borders['close'][] = $pos;
}
// We have a static text entry to move
elseif($current['tag'] == 'TEXT') {
$borders['open'][] = $pos;
$borders['close'][] = $pos;
}
$current = next($_SESSION['currentPDFStructure']);
$pos++;
// Find next section or static text entry in structure
if($current) {
// Next is a section
if($current['tag'] == 'SECTION') {
$borders[$current['type']][] = $pos;
$current = next($_SESSION['currentPDFStructure']);
$pos++;
// Find end of this section
while($current && $current['tag'] != 'SECTION' && $current['type'] != 'close') {
if($current['tag'] == 'SECTION') {
$borders[$current['type']][] = $pos;
}
$current = next($_SESSION['currentPDFStructure']);
$pos++;
}
}
// Next is static text entry
elseif($current['tag'] == 'TEXT') {
$borders['open'][] = $pos;
}
$borders['close'][] = $pos;
}
// Move only downwars if not bottenmost element of this structure
if(count($borders['open']) > 1) {
// Calculate entries to move and move them
$cut_start = $borders['open'][count($borders['open']) - 1];
$cut_count = $borders['close'][count($borders['close']) - 1] - $borders['open'][count($borders['open']) - 1] + 1;
$insert_pos = $borders['open'][count($borders['open']) - 2];
$tomove = array_splice($_SESSION['currentPDFStructure'],$cut_start,$cut_count);
array_splice($_SESSION['currentPDFStructure'],$insert_pos,0,$tomove);
}
}
// We have a value entry to move; move it only if it is not the bottmmost
// element of this section.
elseif($tmp['tag'] == 'ENTRY' && $next['tag'] == 'ENTRY') {
$_SESSION['currentPDFStructure'][$index] = $_SESSION['currentPDFStructure'][$index + 1];
$_SESSION['currentPDFStructure'][$index + 1] = $tmp;
}
}
// Move a section, static text or value entry upwards
elseif (strpos($key, 'up_') === 0) {
$index = substr($key, strlen('up_'));
$tmp = $_SESSION['currentPDFStructure'][$index];
$prev = $_SESSION['currentPDFStructure'][$index - 1];
// We have a section or static text to move
if($tmp['tag'] == 'SECTION' || $tmp['tag'] == 'TEXT') {
$pos = 0;
$borders = array();
$current = current($_SESSION['currentPDFStructure']);
// Add borders of sections and static text entry to array
if($current['tag'] == 'SECTION') {
$borders[$current['type']][] = $pos;
}
elseif($current['tag'] == 'TEXT') {
$borders['open'][] = $pos;
$borders['close'][] = $pos;
}
// Find all sections and statci text fields before the section or static
// text entry to move upwards
while($pos < $index) {
$current = next($_SESSION['currentPDFStructure']);
$pos++;
if($current['tag'] == 'SECTION') {
$borders[$current['type']][] = $pos;
}
elseif($current['tag'] == 'TEXT') {
$borders['open'][] = $pos;
$borders['close'][] = $pos;
}
}
// Move only when not topmost element
if(count($borders['close']) > 0) {
// We have a section to move up
if($current['tag'] == 'SECTION') {
$current = next($_SESSION['currentPDFStructure']);
$pos++;
// Find end of section to move
while($current && $current['tag'] != 'SECTION' && $current['type'] != 'close') {
$current = next($_SESSION['currentPDFStructure']);
$pos++;
}
$borders['close'][] = $pos;
}
// Calculate the entries to move and move them
$cut_start = $borders['open'][count($borders['open']) - 1];
$cut_count = $borders['close'][count($borders['close']) - 1] - $borders['open'][count($borders['open']) - 1] + 1;
$insert_pos = $borders['open'][count($borders['open']) - 2];
$tomove = array_splice($_SESSION['currentPDFStructure'],$cut_start,$cut_count);
array_splice($_SESSION['currentPDFStructure'],$insert_pos,0,$tomove);
}
}
// We have a value entry to move; move it only if its not the topmost
// entry in this section
elseif($tmp['tag'] == 'ENTRY' && $prev['tag'] == 'ENTRY') {
$_SESSION['currentPDFStructure'][$index] = $prev;
$_SESSION['currentPDFStructure'][$index - 1] = $tmp;
}
}
}
// Load PDF structure from file if it is not defined in session
if(!isset($_SESSION['currentPDFStructure'])) {
// Load structure file to be edit
$reader = new PDFStructureReader();
try {
if(isset($_GET['edit'])) {
$_SESSION['currentPDFStructure'] = $reader->read($type->getId(), $_GET['edit']);
$_GET['pdfname'] = $_GET['edit'];
}
// Load default structure file when creating a new one
else {
$_SESSION['currentPDFStructure'] = $reader->read($type->getId(), 'default');
}
}
catch (\LAMException $e) {
metaRefresh('pdfmain.php?loadFailed=1&name=' . $_GET['edit']);
exit;
}
}
if (!empty($_POST['form_submit'])) {
updateBasicSettings($_SESSION['currentPDFStructure']);
@ -280,6 +129,24 @@ if (!empty($_POST['form_submit'])) {
addSection($_SESSION['currentPDFStructure']);
addSectionEntry($_SESSION['currentPDFStructure']);
removeItem($_SESSION['currentPDFStructure']);
moveUp($_SESSION['currentPDFStructure']);
moveDown($_SESSION['currentPDFStructure']);
}
// Check if pdfname is valid, then save current structure to file and go to
// main pdf structure page
$saveErrors = array();
if(isset($_GET['submit'])) {
$writer = new PDFStructureWriter();
try {
$writer->write($type->getId(), $_POST['pdfname'], $_SESSION['currentPDFStructure']);
unset($_SESSION['currentPDFStructure']);
metaRefresh('pdfmain.php?savedSuccessfully=' . $_POST['pdfname']);
exit;
}
catch (\LAMException $e) {
$saveErrors[] = array('ERROR', $e->getTitle(), $e->getMessage());
}
}
$availablePDFFields = getAvailablePDFFields($type->getId());
@ -344,9 +211,6 @@ $structureName = '';
if (isset($_GET['edit'])) {
$structureName = $_GET['edit'];
}
elseif (isset($_GET['pdfname'])) {
$structureName = $_GET['pdfname'];
}
else if (isset($_POST['pdfname'])) {
$structureName = $_POST['pdfname'];
}
@ -748,4 +612,69 @@ function removeItem(&$structure) {
}
}
/**
* Moves up a section or entry if requested.
*
* @param PDFStructure $structure
*/
function moveUp(&$structure) {
$sections = $structure->getSections();
foreach ($_POST as $key => $value) {
// move section
if (strpos($key, 'up_section_') === 0) {
$pos = substr($key, strlen('up_section_'));
$sectionTmp = $sections[$pos - 1];
$sections[$pos - 1] = $sections[$pos];
$sections[$pos] = $sectionTmp;
$structure->setSections($sections);
}
// move section entry
if (strpos($key, 'up_entry_') === 0) {
$parts = substr($key, strlen('up_entry_'));
$parts = explode('_', $parts);
$sectionPos = $parts[0];
$entryPos = $parts[1];
$entries = $sections[$sectionPos]->getEntries();
$entryTmp = $entries[$entryPos - 1];
$entries[$entryPos - 1] = $entries[$entryPos];
$entries[$entryPos] = $entryTmp;
$sections[$sectionPos]->setEntries($entries);
$structure->setSections($sections);
}
}
}
/**
* Moves down a section or entry if requested.
*
* @param PDFStructure $structure
*/
function moveDown(&$structure) {
$sections = $structure->getSections();
foreach ($_POST as $key => $value) {
// move section
if (strpos($key, 'down_section_') === 0) {
$pos = substr($key, strlen('down_section_'));
$sectionTmp = $sections[$pos + 1];
$sections[$pos + 1] = $sections[$pos];
$sections[$pos] = $sectionTmp;
$structure->setSections($sections);
}
// move section entry
if (strpos($key, 'down_entry_') === 0) {
$parts = substr($key, strlen('down_entry_'));
$parts = explode('_', $parts);
$sectionPos = $parts[0];
$entryPos = $parts[1];
$entries = $sections[$sectionPos]->getEntries();
$entries = $sections[$sectionPos]->getEntries();
$entryTmp = $entries[$entryPos + 1];
$entries[$entryPos + 1] = $entries[$entryPos];
$entries[$entryPos] = $entryTmp;
$sections[$sectionPos]->setEntries($entries);
$structure->setSections($sections);
}
}
}
?>

View File

@ -3,6 +3,7 @@ use LAM\PDF\PDFTextSection;
use LAM\PDF\PDFEntrySection;
use LAM\PDF\PDFStructureReader;
use LAM\PDF\PDFStructure;
use LAM\PDF\PDFStructureWriter;
/*
This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
@ -48,7 +49,7 @@ class ReadStructureTest extends PHPUnit_Framework_TestCase {
$this->assertEquals('User information', $structure->getTitle());
$this->assertEquals(PDFStructure::FOLDING_STANDARD, $structure->getFoldingMarks());
$sections = $structure->getSections();
$this->assertEquals(3, sizeof($sections));
$this->assertEquals(4, sizeof($sections));
// check first section
$this->assertInstanceOf(PDFEntrySection::class, $sections[0]);
$this->assertFalse($sections[0]->isAttributeTitle());
@ -69,6 +70,12 @@ class ReadStructureTest extends PHPUnit_Framework_TestCase {
$this->assertEquals(2, sizeof($entries));
$this->assertEquals('posixAccount_homeDirectory', $entries[0]->getKey());
$this->assertEquals('posixAccount_loginShell', $entries[1]->getKey());
// check fourth section
$this->assertInstanceOf(PDFEntrySection::class, $sections[3]);
$this->assertFalse($sections[3]->isAttributeTitle());
$this->assertEquals('No entries', $sections[3]->getTitle());
$entries = $sections[3]->getEntries();
$this->assertEquals(0, sizeof($entries));
}
/**
@ -80,6 +87,28 @@ class ReadStructureTest extends PHPUnit_Framework_TestCase {
return dirname(dirname(__FILE__)) . '/resources/pdf/' . $file;
}
/**
* Tests if the output is the same as the original PDF.
*/
public function testWrite() {
$file = $this->getTestFileName('writer.xml');
// read input XML
$fileHandle = fopen($file, "r");
$originalXML = fread($fileHandle, 1000000);
fclose($fileHandle);
// read structure
$reader = $this->getMockBuilder('\LAM\PDF\PDFStructureReader')
->setMethods(array('getFileName'))
->getMock();
$reader->method('getFileName')->willReturn($file);
$structure = $reader->read('type', 'name');
// create writer and get output XML
$writer = new PDFStructureWriter();
$xml = $writer->getXML($structure);
// compare
$this->assertEquals($originalXML, $xml);
}
}
?>

View File

@ -10,4 +10,5 @@
<entry name="posixAccount_homeDirectory" />
<entry name="posixAccount_loginShell" />
</section>
<section name="No entries"/>
</pdf>

View File

@ -0,0 +1,12 @@
<pdf filename="printLogo.jpg" headline="User information" foldingmarks="standard">
<section name="Personal user information">
<entry name="inetOrgPerson_givenName"/>
<entry name="inetOrgPerson_sn"/>
<entry name="inetOrgPerson_street"/>
</section>
<text>test text</text>
<section name="_posixAccount_uid">
<entry name="posixAccount_homeDirectory"/>
<entry name="posixAccount_loginShell"/>
</section>
</pdf>