| Total Complexity | 69 |
| Total Lines | 475 |
| Duplicated Lines | 0 % |
| Changes | 3 | ||
| Bugs | 0 | Features | 0 |
Complex classes like WBXMLEncoder 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.
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 WBXMLEncoder, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 11 | class WBXMLEncoder extends WBXMLDefs { |
||
| 12 | private $_dtd; |
||
| 13 | private $_tagcp = 0; |
||
| 14 | private $log = false; |
||
| 15 | private $logStack = []; |
||
| 16 | |||
| 17 | // We use a delayed output mechanism in which we only output a tag when it actually has something |
||
| 18 | // in it. This can cause entire XML trees to disappear if they don't have output data in them; Ie |
||
| 19 | // calling 'startTag' 10 times, and then 'endTag' will cause 0 bytes of output apart from the header. |
||
| 20 | |||
| 21 | // Only when content() is called do we output the current stack of tags |
||
| 22 | |||
| 23 | private $_stack; // the content is multipart |
||
| 24 | private $bodyparts; |
||
| 25 | |||
| 26 | public function __construct(private $_out, private $multipart = false) { |
||
| 27 | $this->log = SLog::IsWbxmlDebugEnabled(); |
||
| 28 | |||
| 29 | // reverse-map the DTD |
||
| 30 | foreach ($this->dtd["namespaces"] as $nsid => $nsname) { |
||
| 31 | $this->_dtd["namespaces"][$nsname] = $nsid; |
||
| 32 | } |
||
| 33 | |||
| 34 | foreach ($this->dtd["codes"] as $cp => $value) { |
||
| 35 | $this->_dtd["codes"][$cp] = []; |
||
| 36 | foreach ($this->dtd["codes"][$cp] as $tagid => $tagname) { |
||
| 37 | $this->_dtd["codes"][$cp][$tagname] = $tagid; |
||
| 38 | } |
||
| 39 | } |
||
| 40 | $this->_stack = []; |
||
| 41 | $this->bodyparts = []; |
||
| 42 | } |
||
| 43 | |||
| 44 | /** |
||
| 45 | * Puts the WBXML header on the stream. |
||
| 46 | */ |
||
| 47 | public function startWBXML() { |
||
| 48 | if ($this->multipart) { |
||
| 49 | header("Content-Type: application/vnd.ms-sync.multipart"); |
||
| 50 | SLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->startWBXML() type: vnd.ms-sync.multipart"); |
||
| 51 | } |
||
| 52 | else { |
||
| 53 | header("Content-Type: application/vnd.ms-sync.wbxml"); |
||
| 54 | SLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->startWBXML() type: vnd.ms-sync.wbxml"); |
||
| 55 | } |
||
| 56 | |||
| 57 | $this->outByte(0x03); // WBXML 1.3 |
||
| 58 | $this->outMBUInt(0x01); // Public ID 1 |
||
| 59 | $this->outMBUInt(106); // UTF-8 |
||
| 60 | $this->outMBUInt(0x00); // string table length (0) |
||
| 61 | } |
||
| 62 | |||
| 63 | /** |
||
| 64 | * Puts a StartTag on the output stack. |
||
| 65 | * |
||
| 66 | * @param mixed $tag |
||
| 67 | * @param mixed $attributes |
||
| 68 | * @param mixed $nocontent |
||
| 69 | */ |
||
| 70 | public function startTag($tag, $attributes = false, $nocontent = false) { |
||
|
|
|||
| 71 | $stackelem = []; |
||
| 72 | |||
| 73 | if (!$nocontent) { |
||
| 74 | $stackelem['tag'] = $tag; |
||
| 75 | $stackelem['nocontent'] = $nocontent; |
||
| 76 | $stackelem['sent'] = false; |
||
| 77 | |||
| 78 | array_push($this->_stack, $stackelem); |
||
| 79 | |||
| 80 | // If 'nocontent' is specified, then apparently the user wants to force |
||
| 81 | // output of an empty tag, and we therefore output the stack here |
||
| 82 | } |
||
| 83 | else { |
||
| 84 | $this->_outputStack(); |
||
| 85 | $this->_startTag($tag, $nocontent); |
||
| 86 | } |
||
| 87 | } |
||
| 88 | |||
| 89 | /** |
||
| 90 | * Puts an EndTag on the stack. |
||
| 91 | */ |
||
| 92 | public function endTag() { |
||
| 93 | $stackelem = array_pop($this->_stack); |
||
| 94 | |||
| 95 | // Only output end tags for items that have had a start tag sent |
||
| 96 | if ($stackelem['sent']) { |
||
| 97 | $this->_endTag(); |
||
| 98 | |||
| 99 | if (count($this->_stack) == 0) { |
||
| 100 | SLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->endTag() WBXML output completed"); |
||
| 101 | } |
||
| 102 | if (count($this->_stack) == 0 && $this->multipart == true) { |
||
| 103 | $this->processMultipart(); |
||
| 104 | } |
||
| 105 | if (count($this->_stack) == 0) { |
||
| 106 | $this->writeLog(); |
||
| 107 | } |
||
| 108 | } |
||
| 109 | } |
||
| 110 | |||
| 111 | /** |
||
| 112 | * Puts content on the output stack. |
||
| 113 | * |
||
| 114 | * @param string $content |
||
| 115 | */ |
||
| 116 | public function content($content) { |
||
| 117 | // We need to filter out any \0 chars because it's the string terminator in WBXML. We currently |
||
| 118 | // cannot send \0 characters within the XML content anywhere. |
||
| 119 | $content = str_replace("\0", "", $content); |
||
| 120 | if ("x" . $content == "x") { |
||
| 121 | return; |
||
| 122 | } |
||
| 123 | $this->_outputStack(); |
||
| 124 | $this->_content($content); |
||
| 125 | } |
||
| 126 | |||
| 127 | /** |
||
| 128 | * Puts content of a stream on the output stack AND closes it. |
||
| 129 | * |
||
| 130 | * @param resource $stream |
||
| 131 | * @param bool $asBase64 if true, the data will be encoded as base64, default: false |
||
| 132 | * @param bool $opaque if true, output the opaque data, default: false |
||
| 133 | */ |
||
| 134 | public function contentStream($stream, $asBase64 = false, $opaque = false) { |
||
| 135 | // Do not append filters to opaque data as it might contain null char |
||
| 136 | if (!$asBase64 && !$opaque) { |
||
| 137 | stream_filter_register('replacenullchar', 'ReplaceNullcharFilter'); |
||
| 138 | $rnc_filter = stream_filter_append($stream, 'replacenullchar'); |
||
| 139 | } |
||
| 140 | |||
| 141 | $this->_outputStack(); |
||
| 142 | $this->_contentStream($stream, $asBase64, $opaque); |
||
| 143 | |||
| 144 | if (!$asBase64 && !$opaque) { |
||
| 145 | stream_filter_remove($rnc_filter); |
||
| 146 | } |
||
| 147 | |||
| 148 | fclose($stream); |
||
| 149 | } |
||
| 150 | |||
| 151 | /** |
||
| 152 | * Gets the value of multipart. |
||
| 153 | * |
||
| 154 | * @return bool |
||
| 155 | */ |
||
| 156 | public function getMultipart() { |
||
| 157 | return $this->multipart; |
||
| 158 | } |
||
| 159 | |||
| 160 | /** |
||
| 161 | * Adds a bodypart. |
||
| 162 | * |
||
| 163 | * @param Stream $bp |
||
| 164 | */ |
||
| 165 | public function addBodypartStream($bp) { |
||
| 166 | if (!is_resource($bp)) { |
||
| 167 | throw new WBXMLException("WBXMLEncoder->addBodypartStream(): trying to add a " . gettype($bp) . " instead of a stream"); |
||
| 168 | } |
||
| 169 | if ($this->multipart) { |
||
| 170 | $this->bodyparts[] = $bp; |
||
| 171 | } |
||
| 172 | } |
||
| 173 | |||
| 174 | /** |
||
| 175 | * Gets the number of bodyparts. |
||
| 176 | * |
||
| 177 | * @return int |
||
| 178 | */ |
||
| 179 | public function getBodypartsCount() { |
||
| 180 | return count($this->bodyparts); |
||
| 181 | } |
||
| 182 | |||
| 183 | /*---------------------------------------------------------------------------------------------------------- |
||
| 184 | * Private WBXMLEncoder stuff |
||
| 185 | */ |
||
| 186 | |||
| 187 | /** |
||
| 188 | * Output any tags on the stack that haven't been output yet. |
||
| 189 | */ |
||
| 190 | private function _outputStack() { |
||
| 191 | $stackCount = count($this->_stack); |
||
| 192 | for ($i = 0; $i < $stackCount; ++$i) { |
||
| 193 | if (!$this->_stack[$i]['sent']) { |
||
| 194 | $this->_startTag($this->_stack[$i]['tag'], $this->_stack[$i]['nocontent']); |
||
| 195 | $this->_stack[$i]['sent'] = true; |
||
| 196 | } |
||
| 197 | } |
||
| 198 | } |
||
| 199 | |||
| 200 | /** |
||
| 201 | * Outputs an actual start tag. |
||
| 202 | * |
||
| 203 | * @param mixed $tag |
||
| 204 | * @param mixed $nocontent |
||
| 205 | */ |
||
| 206 | private function _startTag($tag, $nocontent = false) { |
||
| 207 | if ($this->log) { |
||
| 208 | $this->logStartTag($tag, $nocontent); |
||
| 209 | } |
||
| 210 | |||
| 211 | $mapping = $this->getMapping($tag); |
||
| 212 | |||
| 213 | if (!$mapping) { |
||
| 214 | return false; |
||
| 215 | } |
||
| 216 | |||
| 217 | if ($this->_tagcp != $mapping["cp"]) { |
||
| 218 | $this->outSwitchPage($mapping["cp"]); |
||
| 219 | $this->_tagcp = $mapping["cp"]; |
||
| 220 | } |
||
| 221 | |||
| 222 | $code = $mapping["code"]; |
||
| 223 | |||
| 224 | if (!isset($nocontent) || !$nocontent) { |
||
| 225 | $code |= 0x40; |
||
| 226 | } |
||
| 227 | |||
| 228 | $this->outByte($code); |
||
| 229 | } |
||
| 230 | |||
| 231 | /** |
||
| 232 | * Outputs actual data. |
||
| 233 | * |
||
| 234 | * @param string $content |
||
| 235 | */ |
||
| 236 | private function _content($content) { |
||
| 242 | } |
||
| 243 | |||
| 244 | /** |
||
| 245 | * Outputs actual data coming from a stream, optionally encoded as base64. |
||
| 246 | * |
||
| 247 | * @param resource $stream |
||
| 248 | * @param bool $asBase64 |
||
| 249 | * @param mixed $opaque |
||
| 250 | */ |
||
| 251 | private function _contentStream($stream, $asBase64, $opaque) { |
||
| 252 | $stat = fstat($stream); |
||
| 253 | // write full stream, including the finalizing terminator to the output stream (stuff outTermStr() would do) |
||
| 254 | if ($opaque) { |
||
| 255 | $this->outByte(self::WBXML_OPAQUE); |
||
| 256 | $this->outMBUInt($stat['size']); |
||
| 257 | } |
||
| 258 | else { |
||
| 259 | $this->outByte(self::WBXML_STR_I); |
||
| 260 | } |
||
| 261 | |||
| 262 | if ($asBase64) { |
||
| 263 | $out_filter = stream_filter_append($this->_out, 'convert.base64-encode'); |
||
| 264 | } |
||
| 265 | $written = stream_copy_to_stream($stream, $this->_out); |
||
| 266 | if ($asBase64) { |
||
| 267 | stream_filter_remove($out_filter); |
||
| 268 | } |
||
| 269 | if (!$opaque) { |
||
| 270 | fwrite($this->_out, chr(0)); |
||
| 271 | } |
||
| 272 | |||
| 273 | if ($this->log) { |
||
| 274 | // data is out, do some logging |
||
| 275 | $this->logContent(sprintf("<<< written %d of %d bytes of %s data >>>", $written, $stat['size'], $asBase64 ? "base64 encoded" : "plain")); |
||
| 276 | } |
||
| 277 | } |
||
| 278 | |||
| 279 | /** |
||
| 280 | * Outputs an actual end tag. |
||
| 281 | */ |
||
| 282 | private function _endTag() { |
||
| 283 | if ($this->log) { |
||
| 284 | $this->logEndTag(); |
||
| 285 | } |
||
| 286 | $this->outByte(self::WBXML_END); |
||
| 287 | } |
||
| 288 | |||
| 289 | /** |
||
| 290 | * Outputs a byte. |
||
| 291 | * |
||
| 292 | * @param mixed $byte |
||
| 293 | */ |
||
| 294 | private function outByte($byte) { |
||
| 295 | fwrite($this->_out, chr($byte)); |
||
| 296 | } |
||
| 297 | |||
| 298 | /** |
||
| 299 | * Output the multibyte integers to the stream. |
||
| 300 | * |
||
| 301 | * A multi-byte integer consists of a series of octets, |
||
| 302 | * where the most significant bit is the continuation flag |
||
| 303 | * and the remaining seven bits are a scalar value. |
||
| 304 | * The octets are arranged in a big-endian order, |
||
| 305 | * eg, the most significant seven bits are transmitted first. |
||
| 306 | * |
||
| 307 | * @see https://www.w3.org/1999/06/NOTE-wbxml-19990624/#_Toc443384895 |
||
| 308 | * |
||
| 309 | * @param int $uint |
||
| 310 | */ |
||
| 311 | private function outMBUInt($uint) { |
||
| 312 | if ($uint == 0x0) { |
||
| 313 | return $this->outByte($uint); |
||
| 314 | } |
||
| 315 | |||
| 316 | $out = ''; |
||
| 317 | |||
| 318 | for ($i = 0; $uint != 0; ++$i) { |
||
| 319 | $byte = $uint & 0x7F; |
||
| 320 | $uint = $uint >> 7; |
||
| 321 | if ($i == 0) { |
||
| 322 | $out = chr($byte) . $out; |
||
| 323 | } |
||
| 324 | else { |
||
| 325 | $out = chr($byte | 0x80) . $out; |
||
| 326 | } |
||
| 327 | } |
||
| 328 | fwrite($this->_out, $out); |
||
| 329 | } |
||
| 330 | |||
| 331 | /** |
||
| 332 | * Outputs content with string terminator. |
||
| 333 | * |
||
| 334 | * @param mixed $content |
||
| 335 | */ |
||
| 336 | private function outTermStr($content) { |
||
| 337 | fwrite($this->_out, (string) $content); |
||
| 338 | fwrite($this->_out, chr(0)); |
||
| 339 | } |
||
| 340 | |||
| 341 | /** |
||
| 342 | * Switches the codepage. |
||
| 343 | * |
||
| 344 | * @param mixed $page |
||
| 345 | */ |
||
| 346 | private function outSwitchPage($page) { |
||
| 347 | $this->outByte(self::WBXML_SWITCH_PAGE); |
||
| 348 | $this->outByte($page); |
||
| 349 | } |
||
| 350 | |||
| 351 | /** |
||
| 352 | * Get the mapping for a tag. |
||
| 353 | * |
||
| 354 | * @param mixed $tag |
||
| 355 | * |
||
| 356 | * @return array |
||
| 357 | */ |
||
| 358 | private function getMapping($tag) { |
||
| 376 | } |
||
| 377 | |||
| 378 | /** |
||
| 379 | * Split a tag from a the fulltag (namespace + tag). |
||
| 380 | * |
||
| 381 | * @param mixed $fulltag |
||
| 382 | * |
||
| 383 | * @return array keys: 'ns' (namespace), 'tag' (tag) |
||
| 384 | */ |
||
| 385 | private function splitTag($fulltag) { |
||
| 404 | } |
||
| 405 | |||
| 406 | /** |
||
| 407 | * Logs a StartTag to SLog. |
||
| 408 | * |
||
| 409 | * @param mixed $tag |
||
| 410 | * @param mixed $nocontent |
||
| 411 | */ |
||
| 412 | private function logStartTag($tag, $nocontent) { |
||
| 413 | $spaces = str_repeat(" ", count($this->logStack)); |
||
| 414 | if ($nocontent) { |
||
| 415 | SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . " <{$tag}/>"); |
||
| 416 | } |
||
| 417 | else { |
||
| 418 | array_push($this->logStack, $tag); |
||
| 419 | SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . " <{$tag}>"); |
||
| 420 | } |
||
| 421 | } |
||
| 422 | |||
| 423 | /** |
||
| 424 | * Logs a EndTag to SLog. |
||
| 425 | */ |
||
| 426 | private function logEndTag() { |
||
| 427 | $spaces = str_repeat(" ", count($this->logStack)); |
||
| 428 | $tag = array_pop($this->logStack); |
||
| 429 | SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . "</{$tag}>"); |
||
| 430 | } |
||
| 431 | |||
| 432 | /** |
||
| 433 | * Logs content to SLog. |
||
| 434 | * |
||
| 435 | * @param string $content |
||
| 436 | */ |
||
| 437 | private function logContent($content) { |
||
| 438 | $spaces = str_repeat(" ", count($this->logStack)); |
||
| 439 | SLog::Write(LOGLEVEL_WBXML, "O " . $spaces . $content); |
||
| 440 | } |
||
| 441 | |||
| 442 | /** |
||
| 443 | * Processes the multipart response. |
||
| 444 | */ |
||
| 445 | private function processMultipart() { |
||
| 446 | SLog::Write(LOGLEVEL_DEBUG, sprintf("WBXMLEncoder->processMultipart() with %d parts to be processed", $this->getBodypartsCount())); |
||
| 447 | $len = ob_get_length(); |
||
| 448 | $buffer = ob_get_clean(); |
||
| 449 | $nrBodyparts = $this->getBodypartsCount(); |
||
| 450 | $blockstart = (($nrBodyparts + 1) * 2) * 4 + 4; |
||
| 451 | |||
| 452 | fwrite($this->_out, pack("iii", $nrBodyparts + 1, $blockstart, $len)); |
||
| 453 | |||
| 454 | foreach ($this->bodyparts as $i => $bp) { |
||
| 455 | $blockstart = $blockstart + $len; |
||
| 456 | $len = fstat($bp); |
||
| 457 | $len = $len['size'] ?? 0; |
||
| 458 | if ($len == 0) { |
||
| 459 | SLog::Write(LOGLEVEL_WARN, sprintf("WBXMLEncoder->processMultipart(): the length of the body part at position %d is 0", $i)); |
||
| 460 | } |
||
| 461 | fwrite($this->_out, pack("ii", $blockstart, $len)); |
||
| 462 | } |
||
| 463 | |||
| 464 | fwrite($this->_out, $buffer); |
||
| 465 | |||
| 466 | foreach ($this->bodyparts as $bp) { |
||
| 467 | stream_copy_to_stream($bp, $this->_out); |
||
| 468 | fclose($bp); |
||
| 469 | } |
||
| 470 | } |
||
| 471 | |||
| 472 | /** |
||
| 473 | * Writes the sent WBXML data to the log if it is not bigger than 512K. |
||
| 474 | */ |
||
| 475 | private function writeLog() { |
||
| 486 | } |
||
| 487 | } |
||
| 488 |
This check looks for parameters that have been defined for a function or method, but which are not used in the method body.