Passed
Pull Request — master (#29)
by
unknown
02:07
created

DocxMustache   F

Complexity

Total Complexity 70

Size/Duplication

Total Lines 497
Duplicated Lines 4.83 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 70
lcom 1
cbo 5
dl 24
loc 497
rs 2.8
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 1
A SaveOpenXmlFile() 0 13 2
A SaveOpenXmlObjectToFile() 0 8 2
A Execute() 0 6 1
A StoragePath() 0 4 1
A Log() 0 8 2
A CleanUpTmpDirs() 0 13 3
A GetTmpDir() 0 8 1
A CopyTmplate() 0 6 1
A exctractOpenXmlFile() 0 6 1
A ReadOpenXmlFile() 0 16 4
A ReadTeamplate() 0 14 2
A retrieveFilesList() 0 6 3
A SubstituteOpenXmlFile() 0 11 2
A AddContentType() 0 27 5
A FetchReplaceableImages() 0 47 3
A RemoveReplaceImages() 0 15 4
B InsertImages() 0 50 10
A ImageReplacer() 0 47 4
A display_xml_error() 0 27 5
C AnalyseImgUrlString() 24 72 10
A SaveAsPdf() 0 26 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DocxMustache often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DocxMustache, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace WrkLst\DocxMustache;
4
5
use Exception;
6
use Illuminate\Support\Facades\Log;
7
8
//Custom DOCX template class to change content based on mustache templating engine.
9
class DocxMustache
10
{
11
    public $items;
12
    public $word_doc;
13
    public $template_file_name;
14
    public $template_file;
15
    public $local_path;
16
    public $storageDisk;
17
    public $storagePathPrefix;
18
    public $zipper;
19
    public $imageManipulation;
20
    public $verbose;
21
22
    public function __construct($items, $local_template_file)
23
    {
24
        $this->items = $items;
25
        $this->template_file_name = basename($local_template_file);
26
        $this->template_file = $local_template_file;
27
        $this->word_doc = false;
28
        $this->zipper = new \Wrklst\Zipper\Zipper();
29
30
        //name of disk for storage
31
        $this->storageDisk = 'local';
32
33
        //prefix within your storage path
34
        $this->storagePathPrefix = 'app/';
35
36
        //if you use img urls that support manipulation via parameter
37
        $this->imageManipulation = ''; //'&w=1800';
38
39
        $this->verbose = false;
40
    }
41
42
    public function Execute($dpi = 72)
43
    {
44
        $this->CopyTmplate();
45
        $this->zipper->make($this->StoragePath($this->local_path.$this->template_file_name));
46
        $this->ReadTeamplate($dpi);
47
    }
48
49
    /**
50
     * @param string $file
51
     */
52
    public function StoragePath($file)
53
    {
54
        return storage_path($file);
55
    }
56
57
    /**
58
     * @param string $msg
59
     */
60
    protected function Log($msg)
61
    {
62
        //introduce logging method here to keep track of process
63
        // can be overwritten in extended class to log with custom preocess logger
64
        if ($this->verbose) {
65
            Log::error($msg);
66
        }
67
    }
68
69
    public function CleanUpTmpDirs()
70
    {
71
        $now = time();
72
        $isExpired = ($now - (60 * 240));
73
        $disk = \Storage::disk($this->storageDisk);
74
        $all_dirs = $disk->directories($this->storagePathPrefix.'DocxMustache');
75
        foreach ($all_dirs as $dir) {
76
            //delete dirs older than 20min
77
            if ($disk->lastModified($dir) < $isExpired) {
78
                $disk->deleteDirectory($dir);
79
            }
80
        }
81
    }
82
83
    public function GetTmpDir()
84
    {
85
        $this->CleanUpTmpDirs();
86
        $path = $this->storagePathPrefix.'DocxMustache/'.uniqid($this->template_file).'/';
87
        \File::makeDirectory($this->StoragePath($path), 0775, true);
88
89
        return $path;
90
    }
91
92
    public function CopyTmplate()
93
    {
94
        $this->Log('Get Copy of Template');
95
        $this->local_path = $this->GetTmpDir();
96
        \Storage::disk($this->storageDisk)->copy($this->storagePathPrefix.$this->template_file, $this->local_path.$this->template_file_name);
97
    }
98
99
    protected function exctractOpenXmlFile($file)
100
    {
101
        $this->zipper
102
            ->make($this->StoragePath($this->local_path.$this->template_file_name))
103
            ->extractTo($this->StoragePath($this->local_path), [$file], \Wrklst\Zipper\Zipper::WHITELIST);
104
    }
105
106
    protected function ReadOpenXmlFile($file, $type = 'file')
107
    {
108
        if ($type == 'file') {
109
            if ($file_contents = \Storage::disk($this->storageDisk)->get($this->local_path.$file)) {
110
                return $file_contents;
111
            } else {
112
                throw new Exception('Cannot not read file '.$file);
113
            }
114
        } else {
115
            if ($xml_object = simplexml_load_file($this->StoragePath($this->local_path.$file))) {
116
                return $xml_object;
117
            } else {
118
                throw new Exception('Cannot load XML Object from file '.$file);
119
            }
120
        }
121
    }
122
123
    protected function SaveOpenXmlFile($file, $folder, $content)
124
    {
125
        \Storage::disk($this->storageDisk)
126
            ->put($this->local_path.$file, $content);
127
        //add new content to word doc
128
        if ($folder) {
129
            $this->zipper->folder($folder)
130
                ->add($this->StoragePath($this->local_path.$file));
131
        } else {
132
            $this->zipper
133
                ->add($this->StoragePath($this->local_path.$file));
134
        }
135
    }
136
137
    protected function SaveOpenXmlObjectToFile($xmlObject, $file, $folder)
138
    {
139
        if ($xmlString = $xmlObject->asXML()) {
140
            $this->SaveOpenXmlFile($file, $folder, $xmlString);
141
        } else {
142
            throw new Exception('Cannot generate xml for '.$file);
143
        }
144
    }
145
146
    public function ReadTeamplate($dpi)
147
    {
148
        $this->Log('Analyze Template');
149
150
        $this->relevant_files = [];
0 ignored issues
show
Bug introduced by
The property relevant_files does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
151
        //get every File in docx-Archive
152
        $this->zipper
153
            ->getRepository()->each([$this, 'retrieveFilesList']);
154
        foreach($this->relevant_files as $file) {
155
            $this->SubstituteOpenXmlFile($file, $dpi);
156
        }
157
158
        $this->zipper->close();
159
    }
160
161
    public function retrieveFilesList($file, $stats) {
0 ignored issues
show
Unused Code introduced by
The parameter $stats is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
162
        $this->exctractOpenXmlFile($file);
163
        if(substr($file,-3) === 'xml' && substr($file,0,4) === 'word') {
164
            $this->relevant_files[] = $file;
165
        }
166
    }
167
168
    public function SubstituteOpenXmlFile($file, $dpi) {
169
        $this->word_doc = $this->ReadOpenXmlFile($file, 'file');
170
        // $this->Log('Merge Data into Template');
171
        $this->word_doc = MustacheRender::render($this->items, $this->word_doc);
172
173
        $this->word_doc = HtmlConversion::convert($this->word_doc);
174
        if ($file == 'word/document.xml') {
175
            $this->ImageReplacer($dpi);
176
        }
177
        $this->SaveOpenXmlFile($file, 'word', $this->word_doc);
178
    }
179
180
    protected function AddContentType($imageCt = 'jpeg')
181
    {
182
        $ct_file = $this->ReadOpenXmlFile('[Content_Types].xml', 'object');
183
184
        if (!($ct_file instanceof \Traversable)) {
185
            throw new Exception('Cannot traverse through [Content_Types].xml.');
186
        }
187
188
        //check if content type for jpg has been set
189
        $i = 0;
190
        $ct_already_set = false;
191
        foreach ($ct_file as $ct) {
192
            if ((string) $ct_file->Default[$i]['Extension'] == $imageCt) {
0 ignored issues
show
Bug introduced by
Accessing Default on the interface Traversable suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
193
                $ct_already_set = true;
194
            }
195
            $i++;
196
        }
197
198
        //if content type for jpg has not been set, add it to xml
199
        // and save xml to file and add it to the archive
200
        if (!$ct_already_set) {
201
            $sxe = $ct_file->addChild('Default');
202
            $sxe->addAttribute('Extension', $imageCt);
203
            $sxe->addAttribute('ContentType', 'image/'.$imageCt);
204
            $this->SaveOpenXmlObjectToFile($ct_file, '[Content_Types].xml', false);
205
        }
206
    }
207
208
    protected function FetchReplaceableImages(&$main_file, $ns)
209
    {
210
        //set up basic arrays to keep track of imgs
211
        $imgs = [];
212
        $imgs_replaced = []; // so they can later be removed from media and relation file.
213
        $newIdCounter = 1;
214
215
        //iterate through all drawing containers of the xml document
216
        foreach ($main_file->xpath('//w:drawing') as $k=>$drawing) {
217
            //figure out if there is a URL saved in the description field of the img
218
            $img_url = $this->AnalyseImgUrlString($drawing->children($ns['wp'])->xpath('wp:docPr')[0]->attributes()['descr']);
219
            $main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->xpath('wp:docPr')[0]->attributes()['descr'] = $img_url['rest'];
220
221
            //if there is a url, save this img as a img to be replaced
222
            if ($img_url['valid']) {
223
                $ueid = 'wrklstId'.$newIdCounter;
224
                $wasId = (string) $main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->children($ns['a'])->graphic->graphicData->children($ns['pic'])->pic->blipFill->children($ns['a'])->blip->attributes($ns['r'])['embed'];
225
226
                //get dimensions
227
                $cx = (int) $main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->children($ns['a'])->graphic->graphicData->children($ns['pic'])->pic->spPr->children($ns['a'])->xfrm->ext->attributes()['cx'];
228
                $cy = (int) $main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->children($ns['a'])->graphic->graphicData->children($ns['pic'])->pic->spPr->children($ns['a'])->xfrm->ext->attributes()['cy'];
229
230
                //remember img as being replaced
231
                $imgs_replaced[$wasId] = $wasId;
232
233
                //set new img id
234
                $main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->children($ns['a'])->graphic->graphicData->children($ns['pic'])->pic->blipFill->children($ns['a'])->blip->attributes($ns['r'])['embed'] = $ueid;
235
236
                $imgs[] = [
237
                    'cx'     => (int) $cx,
238
                    'cy'     => (int) $cy,
239
                    'wasId'  => $wasId,
240
                    'id'     => $ueid,
241
                    'url'    => $img_url['url'],
242
                    'path'    => $img_url['path'],
243
                    'mode'    => $img_url['mode'],
244
                ];
245
246
                $newIdCounter++;
247
            }
248
        }
249
250
        return [
251
            'imgs'          => $imgs,
252
            'imgs_replaced' => $imgs_replaced,
253
        ];
254
    }
255
256
    protected function RemoveReplaceImages($imgs_replaced, &$rels_file)
257
    {
258
        //TODO: check if the same img is used at a different position int he file as well, as otherwise broken images are produced.
259
        //iterate through replaced images and clean rels files from them
260
        foreach ($imgs_replaced as $img_replaced) {
261
            $i = 0;
262
            foreach ($rels_file as $rel) {
263
                if ((string) $rel->attributes()['Id'] == $img_replaced) {
264
                    $this->zipper->remove('word/'.(string) $rel->attributes()['Target']);
265
                    unset($rels_file->Relationship[$i]);
266
                }
267
                $i++;
268
            }
269
        }
270
    }
271
272
    protected function InsertImages($ns, &$imgs, &$rels_file, &$main_file, $dpi)
273
    {
274
        $docimage = new DocImage();
275
        $allowed_imgs = $docimage->AllowedContentTypeImages();
276
        $image_i = 1;
277
        //iterate through replacable images
278
        foreach ($imgs as $k=>$img) {
279
            $this->Log('Merge Images into Template - '.round($image_i / count($imgs) * 100).'%');
280
            //get file type of img and test it against supported imgs
281
            if ($imgageData = $docimage->GetImageFromUrl($img['mode'] == 'url' ? $img['url'] : $img['path'], $img['mode'] == 'url' ? $this->imageManipulation : '')) {
282
                $imgs[$k]['img_file_src'] = str_replace('wrklstId', 'wrklst_image', $img['id']).$allowed_imgs[$imgageData['mime']];
283
                $imgs[$k]['img_file_dest'] = str_replace('wrklstId', 'wrklst_image', $img['id']).'.jpeg';
284
285
                $resampled_img = $docimage->ResampleImage($this, $imgs, $k, $imgageData['data'], $dpi);
286
287
                $sxe = $rels_file->addChild('Relationship');
288
                $sxe->addAttribute('Id', $img['id']);
289
                $sxe->addAttribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image');
290
                $sxe->addAttribute('Target', 'media/'.$imgs[$k]['img_file_dest']);
291
292
                foreach ($main_file->xpath('//w:drawing') as $k=>$drawing) {
293
                    if (null !== $main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->children($ns['a'])
294
                        ->graphic->graphicData->children($ns['pic'])->pic->blipFill &&
295
                        $img['id'] == $main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->children($ns['a'])
296
                        ->graphic->graphicData->children($ns['pic'])->pic->blipFill->children($ns['a'])
297
                        ->blip->attributes($ns['r'])['embed']) {
298
                        $main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->children($ns['a'])
299
                            ->graphic->graphicData->children($ns['pic'])->pic->spPr->children($ns['a'])
300
                            ->xfrm->ext->attributes()['cx'] = $resampled_img['width_emus'];
301
                        $main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->children($ns['a'])
302
                            ->graphic->graphicData->children($ns['pic'])->pic->spPr->children($ns['a'])
303
                            ->xfrm->ext->attributes()['cy'] = $resampled_img['height_emus'];
304
                        //anchor images
305
                        if (isset($main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->anchor)) {
306
                            $main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->anchor->extent->attributes()['cx'] = $resampled_img['width_emus'];
307
                            $main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->anchor->extent->attributes()['cy'] = $resampled_img['height_emus'];
308
                        }
309
                        //inline images
310
                        elseif (isset($main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->inline)) {
311
                            $main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->inline->extent->attributes()['cx'] = $resampled_img['width_emus'];
312
                            $main_file->xpath('//w:drawing')[$k]->children($ns['wp'])->inline->extent->attributes()['cy'] = $resampled_img['height_emus'];
313
                        }
314
315
                        break;
316
                    }
317
                }
318
            }
319
            $image_i++;
320
        }
321
    }
322
323
    protected function ImageReplacer($dpi)
324
    {
325
        $this->Log('Load XML Document to Merge Images');
326
327
        //load main doc xml
328
        libxml_use_internal_errors(true);
329
        $main_file = simplexml_load_string($this->word_doc);
330
331
        if (gettype($main_file) == 'object') {
332
            $this->Log('Merge Images into Template');
333
334
            //get all namespaces of the document
335
            $ns = $main_file->getNamespaces(true);
336
337
            $replaceableImage = $this->FetchReplaceableImages($main_file, $ns);
338
            $imgs = $replaceableImage['imgs'];
339
            $imgs_replaced = $replaceableImage['imgs_replaced'];
0 ignored issues
show
Unused Code introduced by
$imgs_replaced is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
340
341
            $rels_file = $this->ReadOpenXmlFile('word/_rels/document.xml.rels', 'object');
342
343
            //do not remove until it is checked if the same img is used at a different position int he file as well, as otherwise broken images are produced.
344
            //$this->RemoveReplaceImages($imgs_replaced, $rels_file);
345
346
            //add jpg content type if not set
347
            $this->AddContentType('jpeg');
348
349
            $this->InsertImages($ns, $imgs, $rels_file, $main_file, $dpi);
350
351
            $this->SaveOpenXmlObjectToFile($rels_file, 'word/_rels/document.xml.rels', 'word/_rels');
352
353
            if ($main_file_xml = $main_file->asXML()) {
354
                $this->word_doc = $main_file_xml;
355
            } else {
356
                throw new Exception('Cannot generate xml for word/document.xml.');
357
            }
358
        } else {
359
            $xmlerror = '';
360
            $errors = libxml_get_errors();
361
            foreach ($errors as $error) {
362
                // handle errors here
363
                $xmlerror .= $this->display_xml_error($error, explode("\n", $this->word_doc));
364
            }
365
            libxml_clear_errors();
366
            $this->Log('Error: Could not load XML file. '.$xmlerror);
367
            libxml_clear_errors();
368
        }
369
    }
370
371
    /*
372
    example for extracting xml errors from
373
    http://php.net/manual/en/function.libxml-get-errors.php
374
    */
375
    protected function display_xml_error($error, $xml)
376
    {
377
        $return = $xml[$error->line - 1]."\n";
378
        $return .= str_repeat('-', $error->column)."^\n";
379
380
        switch ($error->level) {
381
            case LIBXML_ERR_WARNING:
382
                $return .= "Warning $error->code: ";
383
                break;
384
                case LIBXML_ERR_ERROR:
385
                $return .= "Error $error->code: ";
386
                break;
387
            case LIBXML_ERR_FATAL:
388
                $return .= "Fatal Error $error->code: ";
389
                break;
390
        }
391
392
        $return .= trim($error->message).
393
                    "\n  Line: $error->line".
394
                    "\n  Column: $error->column";
395
396
        if ($error->file) {
397
            $return .= "\n  File: $error->file";
398
        }
399
400
        return "$return\n\n--------------------------------------------\n\n";
401
    }
402
403
    /**
404
     * @param string $string
405
     */
406
    protected function AnalyseImgUrlString($string)
407
    {
408
        $string = (string) $string;
409
        $start = '[IMG-REPLACE]';
410
        $end = '[/IMG-REPLACE]';
411
        $start_local = '[LOCAL_IMG_REPLACE]';
412
        $end_local = '[/LOCAL_IMG_REPLACE]';
413
        $valid = false;
414
        $url = '';
415
        $path = '';
416
417
        if ($string != str_replace($start, '', $string) && $string == str_replace($start.$end, '', $string)) {
418
            $string = ' '.$string;
419
            $ini = strpos($string, $start);
420 View Code Duplication
            if ($ini == 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
421
                $url = '';
422
                $rest = $string;
423
            } else {
424
                $ini += strlen($start);
425
                $len = ((strpos($string, $end, $ini)) - $ini);
426
                $url = substr($string, $ini, $len);
427
428
                $ini = strpos($string, $start);
429
                $len = strpos($string, $end, $ini + strlen($start)) + strlen($end);
430
                $rest = substr($string, 0, $ini).substr($string, $len);
431
            }
432
433
            $valid = true;
434
435
            //TODO: create a better url validity check
436
            if (!trim(str_replace(['http', 'https', ':', ' '], '', $url)) || $url == str_replace('http', '', $url)) {
437
                $valid = false;
438
            }
439
            $mode = 'url';
440
        } elseif ($string != str_replace($start_local, '', $string) && $string == str_replace($start_local.$end_local, '', $string)) {
441
            $string = ' '.$string;
442
            $ini = strpos($string, $start_local);
443 View Code Duplication
            if ($ini == 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
444
                $path = '';
445
                $rest = $string;
446
            } else {
447
                $ini += strlen($start_local);
448
                $len = ((strpos($string, $end_local, $ini)) - $ini);
449
                $path = str_replace('..', '', substr($string, $ini, $len));
450
451
                $ini = strpos($string, $start_local);
452
                $len = strpos($string, $end_local, $ini + strlen($start)) + strlen($end_local);
453
                $rest = substr($string, 0, $ini).substr($string, $len);
454
            }
455
456
            $valid = true;
457
458
            //check if path starts with storage path
459
            if (!starts_with($path, storage_path())) {
460
                $valid = false;
461
            }
462
            $mode = 'path';
463
        } else {
464
            $mode = 'nothing';
465
            $url = '';
466
            $path = '';
467
            $rest = str_replace([$start, $end, $start_local, $end_local], '', $string);
468
        }
469
470
        return [
471
            'mode' => $mode,
472
            'url'  => trim($url),
473
            'path' => trim($path),
474
            'rest' => trim($rest),
475
            'valid' => $valid,
476
        ];
477
    }
478
479
    public function SaveAsPdf()
480
    {
481
        $this->Log('Converting DOCX to PDF');
482
        //convert to pdf with libre office
483
        $process = new \Symfony\Component\Process\Process([
484
            'soffice',
485
            '--headless',
486
            '--convert-to',
487
            'pdf',
488
            $this->StoragePath($this->local_path.$this->template_file_name),
489
            '--outdir',
490
            $this->StoragePath($this->local_path),
491
        ]);
492
        $process->start();
493
        while ($process->isRunning()) {
494
            //wait until process is ready
495
        }
496
        // executes after the command finishes
497
        if (!$process->isSuccessful()) {
498
            throw new \Symfony\Component\Process\Exception\ProcessFailedException($process);
499
        } else {
500
            $path_parts = pathinfo($this->StoragePath($this->local_path.$this->template_file_name));
501
502
            return $this->StoragePath($this->local_path.$path_parts['filename'].'.pdf');
503
        }
504
    }
505
}
506