Completed
Push — master ( 76b46b...7ee4bf )
by Ben
02:22
created

PdfWriter::writeTrailer()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 19
rs 9.4286
cc 2
eloc 13
nc 2
nop 0
1
<?php
2
/**
3
 * BaconPdf
4
 *
5
 * @link      http://github.com/Bacon/BaconPdf For the canonical source repository
6
 * @copyright 2015 Ben Scholzen (DASPRiD)
7
 * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
8
 */
9
10
namespace Bacon\Pdf;
11
12
use Bacon\Pdf\Encryption\EncryptionInterface;
13
use Bacon\Pdf\Encryption\Pdf16Encryption;
14
use Bacon\Pdf\Encryption\Permissions;
15
use Bacon\Pdf\Writer\ObjectWriter;
16
use SplFileObject;
17
18
class PdfWriter
19
{
20
    /**
21
     * @var ObjectWriter
22
     */
23
    private $objectWriter;
24
25
    /**
26
     * @var PdfWriterOptions
27
     */
28
    private $options;
29
30
    /**
31
     * @var string
32
     */
33
    private $permanentFileIdentifier;
34
35
    /**
36
     * @var string
37
     */
38
    private $changingFileIdentifier;
39
40
    /**
41
     * @var EncryptionInterface|null
42
     */
43
    private $encryption;
44
45
    /**
46
     * @var array
47
     */
48
    private $objectOffsets = [];
49
50
    /**
51
     * @param SplFileObject $fileObject
52
     */
53
    public function __construct(SplFileObject $fileObject, PdfWriterOptions $options)
54
    {
55
        $this->objectWriter = new ObjectWriter($fileObject);
56
57
        $options->freeze();
58
        $this->options = $options;
59
60
        $this->objectWriter->writeRawLine(sprintf("%PDF-%s", $this->options->getVersion()));
61
        $this->objectWriter->writeRawLine("%\xff\xff\xff\xff");
62
63
        $this->permanentFileIdentifier = hex2bin(md5(microtime()));
64
        $this->changingFileIdentifierFileIdentifier = $this->permanentFileIdentifier;
0 ignored issues
show
Bug introduced by
The property changingFileIdentifierFileIdentifier does not seem to exist. Did you mean changingFileIdentifier?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
65
66
        if ($this->options->hasEncryption()) {
67
            $this->encryption = $this->chooseEncryption(
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->chooseEncryption(...>options->getVersion()) can also be of type object<Bacon\Pdf\Pdf14Encryption> or object<Bacon\Pdf\Pdf11Encryption>. However, the property $encryption is declared as type object<Bacon\Pdf\Encrypt...cryptionInterface>|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
68
                $this->options->getUserPassword(),
69
                $this->options->getOwnerPassword(),
70
                $this->options->getVersion()
71
            );
72
        }
73
    }
74
75
    /**
76
     * Closes the document by writing the file trailer.
77
     *
78
     * While the PDF writer will remove all references to the passed in file object in itself to avoid further writing
79
     * and to allow the file pointer to be closed, the callee may still have a reference to it. If that is the case,
80
     * make sure to unset it if you don't need it.
81
     *
82
     * Any further attempts to append data to the PDF writer will result in an exception.
83
     */
84
    public function closeDocument()
85
    {
86
        $xrefOffset = $this->writeXrefTable();
87
        $this->writeTrailer();
88
        $this->writeFooter($xrefOffset);
89
        $this->objectWriter->close();
90
    }
91
92
    /**
93
     * Creates a PDF writer which writes everything to a file.
94
     *
95
     * @param  string $filename
96
     * @return static
97
     */
98
    public static function toFile($filename)
99
    {
100
        return new static(new SplFileObject($filename, 'wb'));
0 ignored issues
show
Bug introduced by
The call to PdfWriter::__construct() misses a required argument $options.

This check looks for function calls that miss required arguments.

Loading history...
101
    }
102
103
    /**
104
     * Creates a PDF writer which outputs everything to the STDOUT.
105
     *
106
     * @return static
107
     */
108
    public static function output()
109
    {
110
        return new static(new SplFileObject('php://stdout', 'wb'));
0 ignored issues
show
Bug introduced by
The call to PdfWriter::__construct() misses a required argument $options.

This check looks for function calls that miss required arguments.

Loading history...
111
    }
112
113
    /**
114
     * Choose an encryption algorithm based on the PDF version.
115
     *
116
     * @param string           $version
117
     * @param string           $userPassword
118
     * @param string|null      $ownerPassword
119
     * @param Permissions|null $userPermissions
120
     */
121
    private function chooseEncryption(
122
        $version,
123
        $userPassword,
124
        $ownerPassword = null,
125
        Permissions $userPermissions = null
126
    ) {
127
        if (version_compare($version, '1.6', '>=')) {
128
            return new Pdf16Encryption($ownerPassword, $userPassword, $version, $userPermissions);
129
        }
130
131
        if (version_compare($version, '1.4', '>=')) {
132
            return new Pdf14Encryption($ownerPassword, $userPassword, $version, $userPermissions);
133
        }
134
135
        return new Pdf11Encryption($ownerPassword, $userPassword, $version, $userPermissions);
136
    }
137
138
    /**
139
     * Writes the xref table.
140
     *
141
     * @return int
142
     */
143
    private function writeXrefTable()
144
    {
145
        $this->objectWriter->ensureBlankLine();
146
        $xrefOffset = $this->objectWriter->currentOffset();
147
148
        $this->objectWriter->writeRawLine('xref');
149
        $this->objectWriter->writeRawLine(sprintf('0 %d', count($this->objectOffsets) + 1));
150
        $this->objectWriter->writeRawLine(sprintf('%010d %05d f ', 0, 65535));
151
152
        foreach ($this->objectOffsets as $offset) {
153
            $this->objectWriter->writeRawLine(sprintf('%010d %05d n ', $offset, 0));
154
        }
155
156
        return $xrefOffset;
157
    }
158
159
    /**
160
     * Writes the trailer.
161
     */
162
    private function writeTrailer()
163
    {
164
        $this->objectWriter->ensureBlankLine();
165
        $this->objectWriter->writeRawLine('trailer');
166
        $this->objectWriter->startDictionary();
167
168
        $this->objectWriter->writeName('Id');
169
        $this->objectWriter->startArray();
170
        $this->objectWriter->writeHexadecimalString($this->permanentFileIdentifier);
171
        $this->objectWriter->writeHexadecimalString($this->changingFileIdentifier);
172
        $this->objectWriter->endArray();
173
174
        if (null !== $this->encryption) {
175
            $this->objectWriter->writeName('Encrypt');
176
            $this->encryption->writeEncryptDictionary($this->objectWriter);
177
        }
178
179
        $this->objectWriter->endDictionary();
180
    }
181
182
    /**
183
     * Writes the footer.
184
     *
185
     * @param int $xrefOffset
186
     */
187
    private function writeFooter($xrefOffset)
188
    {
189
        $this->objectWriter->ensureBlankLine();
190
        $this->objectWriter->writeRawLine('startxref');
191
        $this->objectWriter->writeRawLine((string) $xrefOffset);
192
        $this->objectWriter->writeRawLine("%%%EOF");
193
    }
194
}
195